diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 0deac98210392c8098e281c980cad5d271c2c992..edb9f471e525c55f7119f9da88bb20d614352742 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile
@@ -3,7 +3,8 @@ ifeq ($(CONFIG_ARCH_OMAP2PLUS), y)
 obj-y					+= clk.o autoidle.o clockdomain.o
 clk-common				= dpll.o composite.o divider.o gate.o \
 					  fixed-factor.o mux.o apll.o \
-					  clkt_dpll.o clkt_iclk.o clkt_dflt.o
+					  clkt_dpll.o clkt_iclk.o clkt_dflt.o \
+					  clkctrl.o
 obj-$(CONFIG_SOC_AM33XX)		+= $(clk-common) clk-33xx.o dpll3xxx.o
 obj-$(CONFIG_SOC_TI81XX)		+= $(clk-common) fapll.o clk-814x.o clk-816x.o
 obj-$(CONFIG_ARCH_OMAP2)		+= $(clk-common) interface.o clk-2xxx.o
diff --git a/drivers/clk/ti/clkctrl.c b/drivers/clk/ti/clkctrl.c
new file mode 100644
index 0000000000000000000000000000000000000000..bc399581dabc89b41da7cd7556cd71b5712a84a5
--- /dev/null
+++ b/drivers/clk/ti/clkctrl.c
@@ -0,0 +1,492 @@
+/*
+ * OMAP clkctrl clock support
+ *
+ * Copyright (C) 2017 Texas Instruments, Inc.
+ *
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/clk/ti.h>
+#include <linux/delay.h>
+#include "clock.h"
+
+#define NO_IDLEST			0x1
+
+#define OMAP4_MODULEMODE_MASK		0x3
+
+#define MODULEMODE_HWCTRL		0x1
+#define MODULEMODE_SWCTRL		0x2
+
+#define OMAP4_IDLEST_MASK		(0x3 << 16)
+#define OMAP4_IDLEST_SHIFT		16
+
+#define CLKCTRL_IDLEST_FUNCTIONAL	0x0
+#define CLKCTRL_IDLEST_INTERFACE_IDLE	0x2
+#define CLKCTRL_IDLEST_DISABLED		0x3
+
+/* These timeouts are in us */
+#define OMAP4_MAX_MODULE_READY_TIME	2000
+#define OMAP4_MAX_MODULE_DISABLE_TIME	5000
+
+static bool _early_timeout = true;
+
+struct omap_clkctrl_provider {
+	void __iomem *base;
+	struct list_head clocks;
+};
+
+struct omap_clkctrl_clk {
+	struct clk_hw *clk;
+	u16 reg_offset;
+	int bit_offset;
+	struct list_head node;
+};
+
+union omap4_timeout {
+	u32 cycles;
+	ktime_t start;
+};
+
+static const struct omap_clkctrl_data default_clkctrl_data[] __initconst = {
+	{ 0 },
+};
+
+static u32 _omap4_idlest(u32 val)
+{
+	val &= OMAP4_IDLEST_MASK;
+	val >>= OMAP4_IDLEST_SHIFT;
+
+	return val;
+}
+
+static bool _omap4_is_idle(u32 val)
+{
+	val = _omap4_idlest(val);
+
+	return val == CLKCTRL_IDLEST_DISABLED;
+}
+
+static bool _omap4_is_ready(u32 val)
+{
+	val = _omap4_idlest(val);
+
+	return val == CLKCTRL_IDLEST_FUNCTIONAL ||
+	       val == CLKCTRL_IDLEST_INTERFACE_IDLE;
+}
+
+static bool _omap4_is_timeout(union omap4_timeout *time, u32 timeout)
+{
+	if (unlikely(_early_timeout)) {
+		if (time->cycles++ < timeout) {
+			udelay(1);
+			return false;
+		}
+	} else {
+		if (!ktime_to_ns(time->start)) {
+			time->start = ktime_get();
+			return false;
+		}
+
+		if (ktime_us_delta(ktime_get(), time->start) < timeout) {
+			cpu_relax();
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static int __init _omap4_disable_early_timeout(void)
+{
+	_early_timeout = false;
+
+	return 0;
+}
+arch_initcall(_omap4_disable_early_timeout);
+
+static int _omap4_clkctrl_clk_enable(struct clk_hw *hw)
+{
+	struct clk_hw_omap *clk = to_clk_hw_omap(hw);
+	u32 val;
+	int ret;
+	union omap4_timeout timeout = { 0 };
+
+	if (!clk->enable_bit)
+		return 0;
+
+	if (clk->clkdm) {
+		ret = ti_clk_ll_ops->clkdm_clk_enable(clk->clkdm, hw->clk);
+		if (ret) {
+			WARN(1,
+			     "%s: could not enable %s's clockdomain %s: %d\n",
+			     __func__, clk_hw_get_name(hw),
+			     clk->clkdm_name, ret);
+			return ret;
+		}
+	}
+
+	val = ti_clk_ll_ops->clk_readl(&clk->enable_reg);
+
+	val &= ~OMAP4_MODULEMODE_MASK;
+	val |= clk->enable_bit;
+
+	ti_clk_ll_ops->clk_writel(val, &clk->enable_reg);
+
+	if (clk->flags & NO_IDLEST)
+		return 0;
+
+	/* Wait until module is enabled */
+	while (!_omap4_is_ready(ti_clk_ll_ops->clk_readl(&clk->enable_reg))) {
+		if (_omap4_is_timeout(&timeout, OMAP4_MAX_MODULE_READY_TIME)) {
+			pr_err("%s: failed to enable\n", clk_hw_get_name(hw));
+			return -EBUSY;
+		}
+	}
+
+	return 0;
+}
+
+static void _omap4_clkctrl_clk_disable(struct clk_hw *hw)
+{
+	struct clk_hw_omap *clk = to_clk_hw_omap(hw);
+	u32 val;
+	union omap4_timeout timeout = { 0 };
+
+	if (!clk->enable_bit)
+		return;
+
+	val = ti_clk_ll_ops->clk_readl(&clk->enable_reg);
+
+	val &= ~OMAP4_MODULEMODE_MASK;
+
+	ti_clk_ll_ops->clk_writel(val, &clk->enable_reg);
+
+	if (clk->flags & NO_IDLEST)
+		goto exit;
+
+	/* Wait until module is disabled */
+	while (!_omap4_is_idle(ti_clk_ll_ops->clk_readl(&clk->enable_reg))) {
+		if (_omap4_is_timeout(&timeout,
+				      OMAP4_MAX_MODULE_DISABLE_TIME)) {
+			pr_err("%s: failed to disable\n", clk_hw_get_name(hw));
+			break;
+		}
+	}
+
+exit:
+	if (clk->clkdm)
+		ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk);
+}
+
+static int _omap4_clkctrl_clk_is_enabled(struct clk_hw *hw)
+{
+	struct clk_hw_omap *clk = to_clk_hw_omap(hw);
+	u32 val;
+
+	val = ti_clk_ll_ops->clk_readl(&clk->enable_reg);
+
+	if (val & clk->enable_bit)
+		return 1;
+
+	return 0;
+}
+
+static const struct clk_ops omap4_clkctrl_clk_ops = {
+	.enable		= _omap4_clkctrl_clk_enable,
+	.disable	= _omap4_clkctrl_clk_disable,
+	.is_enabled	= _omap4_clkctrl_clk_is_enabled,
+};
+
+static struct clk_hw *_ti_omap4_clkctrl_xlate(struct of_phandle_args *clkspec,
+					      void *data)
+{
+	struct omap_clkctrl_provider *provider = data;
+	struct omap_clkctrl_clk *entry;
+
+	if (clkspec->args_count != 2)
+		return ERR_PTR(-EINVAL);
+
+	pr_debug("%s: looking for %x:%x\n", __func__,
+		 clkspec->args[0], clkspec->args[1]);
+
+	list_for_each_entry(entry, &provider->clocks, node) {
+		if (entry->reg_offset == clkspec->args[0] &&
+		    entry->bit_offset == clkspec->args[1])
+			break;
+	}
+
+	if (!entry)
+		return ERR_PTR(-EINVAL);
+
+	return entry->clk;
+}
+
+static int __init
+_ti_clkctrl_clk_register(struct omap_clkctrl_provider *provider,
+			 struct device_node *node, struct clk_hw *clk_hw,
+			 u16 offset, u8 bit, const char * const *parents,
+			 int num_parents, const struct clk_ops *ops)
+{
+	struct clk_init_data init = { NULL };
+	struct clk *clk;
+	struct omap_clkctrl_clk *clkctrl_clk;
+	int ret = 0;
+
+	init.name = kasprintf(GFP_KERNEL, "%s:%s:%04x:%d", node->parent->name,
+			      node->name, offset, bit);
+	clkctrl_clk = kzalloc(sizeof(*clkctrl_clk), GFP_KERNEL);
+	if (!init.name || !clkctrl_clk) {
+		ret = -ENOMEM;
+		goto cleanup;
+	}
+
+	clk_hw->init = &init;
+	init.parent_names = parents;
+	init.num_parents = num_parents;
+	init.ops = ops;
+	init.flags = CLK_IS_BASIC;
+
+	clk = ti_clk_register(NULL, clk_hw, init.name);
+	if (IS_ERR_OR_NULL(clk)) {
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	clkctrl_clk->reg_offset = offset;
+	clkctrl_clk->bit_offset = bit;
+	clkctrl_clk->clk = clk_hw;
+
+	list_add(&clkctrl_clk->node, &provider->clocks);
+
+	return 0;
+
+cleanup:
+	kfree(init.name);
+	kfree(clkctrl_clk);
+	return ret;
+}
+
+static void __init
+_ti_clkctrl_setup_gate(struct omap_clkctrl_provider *provider,
+		       struct device_node *node, u16 offset,
+		       const struct omap_clkctrl_bit_data *data,
+		       void __iomem *reg)
+{
+	struct clk_hw_omap *clk_hw;
+
+	clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
+	if (!clk_hw)
+		return;
+
+	clk_hw->enable_bit = data->bit;
+	clk_hw->enable_reg.ptr = reg;
+
+	if (_ti_clkctrl_clk_register(provider, node, &clk_hw->hw, offset,
+				     data->bit, data->parents, 1,
+				     &omap_gate_clk_ops))
+		kfree(clk_hw);
+}
+
+static void __init
+_ti_clkctrl_setup_mux(struct omap_clkctrl_provider *provider,
+		      struct device_node *node, u16 offset,
+		      const struct omap_clkctrl_bit_data *data,
+		      void __iomem *reg)
+{
+	struct clk_omap_mux *mux;
+	int num_parents = 0;
+	const char * const *pname;
+
+	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+	if (!mux)
+		return;
+
+	pname = data->parents;
+	while (*pname) {
+		num_parents++;
+		pname++;
+	}
+
+	mux->mask = num_parents;
+	mux->mask = (1 << fls(mux->mask)) - 1;
+
+	mux->shift = data->bit;
+	mux->reg.ptr = reg;
+
+	if (_ti_clkctrl_clk_register(provider, node, &mux->hw, offset,
+				     data->bit, data->parents, num_parents,
+				     &ti_clk_mux_ops))
+		kfree(mux);
+}
+
+static void __init
+_ti_clkctrl_setup_div(struct omap_clkctrl_provider *provider,
+		      struct device_node *node, u16 offset,
+		      const struct omap_clkctrl_bit_data *data,
+		      void __iomem *reg)
+{
+	struct clk_omap_divider *div;
+	const struct omap_clkctrl_div_data *div_data = data->data;
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		return;
+
+	div->reg.ptr = reg;
+	div->shift = data->bit;
+
+	if (ti_clk_parse_divider_data((int *)div_data->dividers,
+				      div_data->max_div, 0, 0,
+				      &div->width, &div->table)) {
+		pr_err("%s: Data parsing for %s:%04x:%d failed\n", __func__,
+		       node->name, offset, data->bit);
+		kfree(div);
+		return;
+	}
+
+	if (_ti_clkctrl_clk_register(provider, node, &div->hw, offset,
+				     data->bit, data->parents, 1,
+				     &ti_clk_divider_ops))
+		kfree(div);
+}
+
+static void __init
+_ti_clkctrl_setup_subclks(struct omap_clkctrl_provider *provider,
+			  struct device_node *node,
+			  const struct omap_clkctrl_reg_data *data,
+			  void __iomem *reg)
+{
+	const struct omap_clkctrl_bit_data *bits = data->bit_data;
+
+	if (!bits)
+		return;
+
+	while (bits->bit) {
+		switch (bits->type) {
+		case TI_CLK_GATE:
+			_ti_clkctrl_setup_gate(provider, node, data->offset,
+					       bits, reg);
+			break;
+
+		case TI_CLK_DIVIDER:
+			_ti_clkctrl_setup_div(provider, node, data->offset,
+					      bits, reg);
+			break;
+
+		case TI_CLK_MUX:
+			_ti_clkctrl_setup_mux(provider, node, data->offset,
+					      bits, reg);
+			break;
+
+		default:
+			pr_err("%s: bad subclk type: %d\n", __func__,
+			       bits->type);
+			return;
+		}
+		bits++;
+	}
+}
+
+static void __init _ti_omap4_clkctrl_setup(struct device_node *node)
+{
+	struct omap_clkctrl_provider *provider;
+	const struct omap_clkctrl_data *data = default_clkctrl_data;
+	const struct omap_clkctrl_reg_data *reg_data;
+	struct clk_init_data init = { NULL };
+	struct clk_hw_omap *hw;
+	struct clk *clk;
+	struct omap_clkctrl_clk *clkctrl_clk;
+	const __be32 *addrp;
+	u32 addr;
+
+	addrp = of_get_address(node, 0, NULL, NULL);
+	addr = (u32)of_translate_address(node, addrp);
+
+	while (data->addr) {
+		if (addr == data->addr)
+			break;
+
+		data++;
+	}
+
+	if (!data->addr) {
+		pr_err("%s not found from clkctrl data.\n", node->name);
+		return;
+	}
+
+	provider = kzalloc(sizeof(*provider), GFP_KERNEL);
+	if (!provider)
+		return;
+
+	provider->base = of_iomap(node, 0);
+
+	INIT_LIST_HEAD(&provider->clocks);
+
+	/* Generate clocks */
+	reg_data = data->regs;
+
+	while (reg_data->parent) {
+		hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+		if (!hw)
+			return;
+
+		hw->enable_reg.ptr = provider->base + reg_data->offset;
+
+		_ti_clkctrl_setup_subclks(provider, node, reg_data,
+					  hw->enable_reg.ptr);
+
+		if (reg_data->flags & CLKF_SW_SUP)
+			hw->enable_bit = MODULEMODE_SWCTRL;
+		if (reg_data->flags & CLKF_HW_SUP)
+			hw->enable_bit = MODULEMODE_HWCTRL;
+		if (reg_data->flags & CLKF_NO_IDLEST)
+			hw->flags |= NO_IDLEST;
+
+		init.parent_names = &reg_data->parent;
+		init.num_parents = 1;
+		init.flags = 0;
+		init.name = kasprintf(GFP_KERNEL, "%s:%s:%04x:%d",
+				      node->parent->name, node->name,
+				      reg_data->offset, 0);
+		clkctrl_clk = kzalloc(sizeof(*clkctrl_clk), GFP_KERNEL);
+		if (!init.name || !clkctrl_clk)
+			goto cleanup;
+
+		init.ops = &omap4_clkctrl_clk_ops;
+		hw->hw.init = &init;
+
+		clk = ti_clk_register(NULL, &hw->hw, init.name);
+		if (IS_ERR_OR_NULL(clk))
+			goto cleanup;
+
+		clkctrl_clk->reg_offset = reg_data->offset;
+		clkctrl_clk->clk = &hw->hw;
+
+		list_add(&clkctrl_clk->node, &provider->clocks);
+
+		reg_data++;
+	}
+
+	of_clk_add_hw_provider(node, _ti_omap4_clkctrl_xlate, provider);
+	return;
+
+cleanup:
+	kfree(hw);
+	kfree(init.name);
+	kfree(clkctrl_clk);
+}
+CLK_OF_DECLARE(ti_omap4_clkctrl_clock, "ti,clkctrl",
+	       _ti_omap4_clkctrl_setup);
diff --git a/drivers/clk/ti/clock.h b/drivers/clk/ti/clock.h
index 3f7b26540be86d01ad5d7d2a93884a445290ff84..1aa8d577b67f34239e6e1c8db4053e5447b88bb7 100644
--- a/drivers/clk/ti/clock.h
+++ b/drivers/clk/ti/clock.h
@@ -203,6 +203,35 @@ struct ti_dt_clk {
 		.node_name = name,	\
 	}
 
+/* CLKCTRL type definitions */
+struct omap_clkctrl_div_data {
+	const int *dividers;
+	int max_div;
+};
+
+struct omap_clkctrl_bit_data {
+	u8 bit;
+	u8 type;
+	const char * const *parents;
+	const void *data;
+};
+
+struct omap_clkctrl_reg_data {
+	u16 offset;
+	const struct omap_clkctrl_bit_data *bit_data;
+	u16 flags;
+	const char *parent;
+};
+
+struct omap_clkctrl_data {
+	u32 addr;
+	const struct omap_clkctrl_reg_data *regs;
+};
+
+#define CLKF_SW_SUP	BIT(0)
+#define CLKF_HW_SUP	BIT(1)
+#define CLKF_NO_IDLEST	BIT(2)
+
 typedef void (*ti_of_clk_init_cb_t)(struct clk_hw *, struct device_node *);
 
 struct clk *ti_clk_register_gate(struct ti_clk *setup);