Skip to content

Commit

Permalink
[media] omap3isp: Use the common clock framework
Browse files Browse the repository at this point in the history
Expose the two ISP external clocks XCLKA and XCLKB as common clocks for
subdev drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Mike Turquette <mturquette@linaro.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
  • Loading branch information
Laurent Pinchart authored and Mauro Carvalho Chehab committed Apr 14, 2013
1 parent 4290fd1 commit 9b28ee3
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 84 deletions.
277 changes: 202 additions & 75 deletions drivers/media/platform/omap3isp/isp.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#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>
Expand Down Expand Up @@ -148,6 +149,201 @@ void omap3isp_flush(struct isp_device *isp)
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
Expand Down Expand Up @@ -180,80 +376,6 @@ static void isp_disable_interrupts(struct isp_device *isp)
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
Expand Down Expand Up @@ -1969,6 +2091,7 @@ static int isp_remove(struct platform_device *pdev)

isp_unregister_entities(isp);
isp_cleanup_modules(isp);
isp_xclk_cleanup(isp);

__omap3isp_get(isp, false);
iommu_detach_device(isp->domain, &pdev->dev);
Expand Down Expand Up @@ -2042,7 +2165,6 @@ static int isp_probe(struct platform_device *pdev)
}

isp->autoidle = autoidle;
isp->platform_cb.set_xclk = isp_set_xclk;

mutex_init(&isp->isp_mutex);
spin_lock_init(&isp->stat_lock);
Expand Down Expand Up @@ -2093,6 +2215,10 @@ static int isp_probe(struct platform_device *pdev)
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)
Expand Down Expand Up @@ -2162,6 +2288,7 @@ static int isp_probe(struct platform_device *pdev)
free_domain:
iommu_domain_free(isp->domain);
error_isp:
isp_xclk_cleanup(isp);
omap3isp_put(isp);
error:
platform_set_drvdata(pdev, NULL);
Expand Down
22 changes: 17 additions & 5 deletions drivers/media/platform/omap3isp/isp.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <media/omap3isp.h>
#include <media/v4l2-device.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/iommu.h>
Expand Down Expand Up @@ -125,8 +126,20 @@ struct isp_reg {
u32 val;
};

struct isp_platform_callback {
u32 (*set_xclk)(struct isp_device *isp, u32 xclk, u8 xclksel);
enum isp_xclk_id {
ISP_XCLK_A,
ISP_XCLK_B,
};

struct isp_xclk {
struct isp_device *isp;
struct clk_hw hw;
struct clk_lookup *lookup;
enum isp_xclk_id id;

spinlock_t lock; /* Protects enabled and divider */
bool enabled;
unsigned int divider;
};

/*
Expand All @@ -149,6 +162,7 @@ struct isp_platform_callback {
* @cam_mclk: Pointer to camera functional clock structure.
* @csi2_fck: Pointer to camera CSI2 complexIO clock structure.
* @l3_ick: Pointer to OMAP3 L3 bus interface clock.
* @xclks: External clocks provided by the ISP
* @irq: Currently attached ISP ISR callbacks information structure.
* @isp_af: Pointer to current settings for ISP AutoFocus SCM.
* @isp_hist: Pointer to current settings for ISP Histogram SCM.
Expand Down Expand Up @@ -185,12 +199,12 @@ struct isp_device {
int has_context;
int ref_count;
unsigned int autoidle;
u32 xclk_divisor[2]; /* Two clocks, a and b. */
#define ISP_CLK_CAM_ICK 0
#define ISP_CLK_CAM_MCLK 1
#define ISP_CLK_CSI2_FCK 2
#define ISP_CLK_L3_ICK 3
struct clk *clock[4];
struct isp_xclk xclks[2];

/* ISP modules */
struct ispstat isp_af;
Expand All @@ -209,8 +223,6 @@ struct isp_device {
unsigned int subclk_resources;

struct iommu_domain *domain;

struct isp_platform_callback platform_cb;
};

#define v4l2_dev_to_isp_device(dev) \
Expand Down
Loading

0 comments on commit 9b28ee3

Please sign in to comment.