#include <asm/cacheflush.h>
 
 #include <linux/clk.h>
+#include <linux/clkdev.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/dma-mapping.h>
        isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION);
 }
 
+/* -----------------------------------------------------------------------------
+ * XCLK
+ */
+
+#define to_isp_xclk(_hw)       container_of(_hw, struct isp_xclk, hw)
+
+static void isp_xclk_update(struct isp_xclk *xclk, u32 divider)
+{
+       switch (xclk->id) {
+       case ISP_XCLK_A:
+               isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
+                               ISPTCTRL_CTRL_DIVA_MASK,
+                               divider << ISPTCTRL_CTRL_DIVA_SHIFT);
+               break;
+       case ISP_XCLK_B:
+               isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
+                               ISPTCTRL_CTRL_DIVB_MASK,
+                               divider << ISPTCTRL_CTRL_DIVB_SHIFT);
+               break;
+       }
+}
+
+static int isp_xclk_prepare(struct clk_hw *hw)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+
+       omap3isp_get(xclk->isp);
+
+       return 0;
+}
+
+static void isp_xclk_unprepare(struct clk_hw *hw)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+
+       omap3isp_put(xclk->isp);
+}
+
+static int isp_xclk_enable(struct clk_hw *hw)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+       unsigned long flags;
+
+       spin_lock_irqsave(&xclk->lock, flags);
+       isp_xclk_update(xclk, xclk->divider);
+       xclk->enabled = true;
+       spin_unlock_irqrestore(&xclk->lock, flags);
+
+       return 0;
+}
+
+static void isp_xclk_disable(struct clk_hw *hw)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+       unsigned long flags;
+
+       spin_lock_irqsave(&xclk->lock, flags);
+       isp_xclk_update(xclk, 0);
+       xclk->enabled = false;
+       spin_unlock_irqrestore(&xclk->lock, flags);
+}
+
+static unsigned long isp_xclk_recalc_rate(struct clk_hw *hw,
+                                         unsigned long parent_rate)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+
+       return parent_rate / xclk->divider;
+}
+
+static u32 isp_xclk_calc_divider(unsigned long *rate, unsigned long parent_rate)
+{
+       u32 divider;
+
+       if (*rate >= parent_rate) {
+               *rate = parent_rate;
+               return ISPTCTRL_CTRL_DIV_BYPASS;
+       }
+
+       divider = DIV_ROUND_CLOSEST(parent_rate, *rate);
+       if (divider >= ISPTCTRL_CTRL_DIV_BYPASS)
+               divider = ISPTCTRL_CTRL_DIV_BYPASS - 1;
+
+       *rate = parent_rate / divider;
+       return divider;
+}
+
+static long isp_xclk_round_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long *parent_rate)
+{
+       isp_xclk_calc_divider(&rate, *parent_rate);
+       return rate;
+}
+
+static int isp_xclk_set_rate(struct clk_hw *hw, unsigned long rate,
+                            unsigned long parent_rate)
+{
+       struct isp_xclk *xclk = to_isp_xclk(hw);
+       unsigned long flags;
+       u32 divider;
+
+       divider = isp_xclk_calc_divider(&rate, parent_rate);
+
+       spin_lock_irqsave(&xclk->lock, flags);
+
+       xclk->divider = divider;
+       if (xclk->enabled)
+               isp_xclk_update(xclk, divider);
+
+       spin_unlock_irqrestore(&xclk->lock, flags);
+
+       dev_dbg(xclk->isp->dev, "%s: cam_xclk%c set to %lu Hz (div %u)\n",
+               __func__, xclk->id == ISP_XCLK_A ? 'a' : 'b', rate, divider);
+       return 0;
+}
+
+static const struct clk_ops isp_xclk_ops = {
+       .prepare = isp_xclk_prepare,
+       .unprepare = isp_xclk_unprepare,
+       .enable = isp_xclk_enable,
+       .disable = isp_xclk_disable,
+       .recalc_rate = isp_xclk_recalc_rate,
+       .round_rate = isp_xclk_round_rate,
+       .set_rate = isp_xclk_set_rate,
+};
+
+static const char *isp_xclk_parent_name = "cam_mclk";
+
+static const struct clk_init_data isp_xclk_init_data = {
+       .name = "cam_xclk",
+       .ops = &isp_xclk_ops,
+       .parent_names = &isp_xclk_parent_name,
+       .num_parents = 1,
+};
+
+static int isp_xclk_init(struct isp_device *isp)
+{
+       struct isp_platform_data *pdata = isp->pdata;
+       struct clk_init_data init;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) {
+               struct isp_xclk *xclk = &isp->xclks[i];
+               struct clk *clk;
+
+               xclk->isp = isp;
+               xclk->id = i == 0 ? ISP_XCLK_A : ISP_XCLK_B;
+               xclk->divider = 1;
+               spin_lock_init(&xclk->lock);
+
+               init.name = i == 0 ? "cam_xclka" : "cam_xclkb";
+               init.ops = &isp_xclk_ops;
+               init.parent_names = &isp_xclk_parent_name;
+               init.num_parents = 1;
+
+               xclk->hw.init = &init;
+
+               clk = devm_clk_register(isp->dev, &xclk->hw);
+               if (IS_ERR(clk))
+                       return PTR_ERR(clk);
+
+               if (pdata->xclks[i].con_id == NULL &&
+                   pdata->xclks[i].dev_id == NULL)
+                       continue;
+
+               xclk->lookup = kzalloc(sizeof(*xclk->lookup), GFP_KERNEL);
+               if (xclk->lookup == NULL)
+                       return -ENOMEM;
+
+               xclk->lookup->con_id = pdata->xclks[i].con_id;
+               xclk->lookup->dev_id = pdata->xclks[i].dev_id;
+               xclk->lookup->clk = clk;
+
+               clkdev_add(xclk->lookup);
+       }
+
+       return 0;
+}
+
+static void isp_xclk_cleanup(struct isp_device *isp)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) {
+               struct isp_xclk *xclk = &isp->xclks[i];
+
+               if (xclk->lookup)
+                       clkdev_drop(xclk->lookup);
+       }
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupts
+ */
+
 /*
  * isp_enable_interrupts - Enable ISP interrupts.
  * @isp: OMAP3 ISP device
        isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE);
 }
 
-/**
- * isp_set_xclk - Configures the specified cam_xclk to the desired frequency.
- * @isp: OMAP3 ISP device
- * @xclk: Desired frequency of the clock in Hz. 0 = stable low, 1 is stable high
- * @xclksel: XCLK to configure (0 = A, 1 = B).
- *
- * Configures the specified MCLK divisor in the ISP timing control register
- * (TCTRL_CTRL) to generate the desired xclk clock value.
- *
- * Divisor = cam_mclk_hz / xclk
- *
- * Returns the final frequency that is actually being generated
- **/
-static u32 isp_set_xclk(struct isp_device *isp, u32 xclk, u8 xclksel)
-{
-       u32 divisor;
-       u32 currentxclk;
-       unsigned long mclk_hz;
-
-       if (!omap3isp_get(isp))
-               return 0;
-
-       mclk_hz = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]);
-
-       if (xclk >= mclk_hz) {
-               divisor = ISPTCTRL_CTRL_DIV_BYPASS;
-               currentxclk = mclk_hz;
-       } else if (xclk >= 2) {
-               divisor = mclk_hz / xclk;
-               if (divisor >= ISPTCTRL_CTRL_DIV_BYPASS)
-                       divisor = ISPTCTRL_CTRL_DIV_BYPASS - 1;
-               currentxclk = mclk_hz / divisor;
-       } else {
-               divisor = xclk;
-               currentxclk = 0;
-       }
-
-       switch (xclksel) {
-       case ISP_XCLK_A:
-               isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
-                               ISPTCTRL_CTRL_DIVA_MASK,
-                               divisor << ISPTCTRL_CTRL_DIVA_SHIFT);
-               dev_dbg(isp->dev, "isp_set_xclk(): cam_xclka set to %d Hz\n",
-                       currentxclk);
-               break;
-       case ISP_XCLK_B:
-               isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
-                               ISPTCTRL_CTRL_DIVB_MASK,
-                               divisor << ISPTCTRL_CTRL_DIVB_SHIFT);
-               dev_dbg(isp->dev, "isp_set_xclk(): cam_xclkb set to %d Hz\n",
-                       currentxclk);
-               break;
-       case ISP_XCLK_NONE:
-       default:
-               omap3isp_put(isp);
-               dev_dbg(isp->dev, "ISP_ERR: isp_set_xclk(): Invalid requested "
-                       "xclk. Must be 0 (A) or 1 (B).\n");
-               return -EINVAL;
-       }
-
-       /* Do we go from stable whatever to clock? */
-       if (divisor >= 2 && isp->xclk_divisor[xclksel - 1] < 2)
-               omap3isp_get(isp);
-       /* Stopping the clock. */
-       else if (divisor < 2 && isp->xclk_divisor[xclksel - 1] >= 2)
-               omap3isp_put(isp);
-
-       isp->xclk_divisor[xclksel - 1] = divisor;
-
-       omap3isp_put(isp);
-
-       return currentxclk;
-}
-
 /*
  * isp_core_init - ISP core settings
  * @isp: OMAP3 ISP device
 
        isp_unregister_entities(isp);
        isp_cleanup_modules(isp);
+       isp_xclk_cleanup(isp);
 
        __omap3isp_get(isp, false);
        iommu_detach_device(isp->domain, &pdev->dev);
        }
 
        isp->autoidle = autoidle;
-       isp->platform_cb.set_xclk = isp_set_xclk;
 
        mutex_init(&isp->isp_mutex);
        spin_lock_init(&isp->stat_lock);
        if (ret < 0)
                goto error_isp;
 
+       ret = isp_xclk_init(isp);
+       if (ret < 0)
+               goto error_isp;
+
        /* Memory resources */
        for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++)
                if (isp->revision == isp_res_maps[m].isp_rev)
 free_domain:
        iommu_domain_free(isp->domain);
 error_isp:
+       isp_xclk_cleanup(isp);
        omap3isp_put(isp);
 error:
        platform_set_drvdata(pdev, NULL);