[media] omap3isp: Use the common clock framework
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>
This commit is contained in:
parent
4290fd1a56
commit
9b28ee3c91
@ -55,6 +55,7 @@
|
|||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
#include <linux/clkdev.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/dma-mapping.h>
|
#include <linux/dma-mapping.h>
|
||||||
@ -148,6 +149,201 @@ void omap3isp_flush(struct isp_device *isp)
|
|||||||
isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION);
|
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_enable_interrupts - Enable ISP interrupts.
|
||||||
* @isp: OMAP3 ISP device
|
* @isp: OMAP3 ISP device
|
||||||
@ -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_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_core_init - ISP core settings
|
||||||
* @isp: OMAP3 ISP device
|
* @isp: OMAP3 ISP device
|
||||||
@ -1969,6 +2091,7 @@ static int isp_remove(struct platform_device *pdev)
|
|||||||
|
|
||||||
isp_unregister_entities(isp);
|
isp_unregister_entities(isp);
|
||||||
isp_cleanup_modules(isp);
|
isp_cleanup_modules(isp);
|
||||||
|
isp_xclk_cleanup(isp);
|
||||||
|
|
||||||
__omap3isp_get(isp, false);
|
__omap3isp_get(isp, false);
|
||||||
iommu_detach_device(isp->domain, &pdev->dev);
|
iommu_detach_device(isp->domain, &pdev->dev);
|
||||||
@ -2042,7 +2165,6 @@ static int isp_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
isp->autoidle = autoidle;
|
isp->autoidle = autoidle;
|
||||||
isp->platform_cb.set_xclk = isp_set_xclk;
|
|
||||||
|
|
||||||
mutex_init(&isp->isp_mutex);
|
mutex_init(&isp->isp_mutex);
|
||||||
spin_lock_init(&isp->stat_lock);
|
spin_lock_init(&isp->stat_lock);
|
||||||
@ -2093,6 +2215,10 @@ static int isp_probe(struct platform_device *pdev)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto error_isp;
|
goto error_isp;
|
||||||
|
|
||||||
|
ret = isp_xclk_init(isp);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error_isp;
|
||||||
|
|
||||||
/* Memory resources */
|
/* Memory resources */
|
||||||
for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++)
|
for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++)
|
||||||
if (isp->revision == isp_res_maps[m].isp_rev)
|
if (isp->revision == isp_res_maps[m].isp_rev)
|
||||||
@ -2162,6 +2288,7 @@ detach_dev:
|
|||||||
free_domain:
|
free_domain:
|
||||||
iommu_domain_free(isp->domain);
|
iommu_domain_free(isp->domain);
|
||||||
error_isp:
|
error_isp:
|
||||||
|
isp_xclk_cleanup(isp);
|
||||||
omap3isp_put(isp);
|
omap3isp_put(isp);
|
||||||
error:
|
error:
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include <media/omap3isp.h>
|
#include <media/omap3isp.h>
|
||||||
#include <media/v4l2-device.h>
|
#include <media/v4l2-device.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
@ -125,8 +126,20 @@ struct isp_reg {
|
|||||||
u32 val;
|
u32 val;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct isp_platform_callback {
|
enum isp_xclk_id {
|
||||||
u32 (*set_xclk)(struct isp_device *isp, u32 xclk, u8 xclksel);
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -149,6 +162,7 @@ struct isp_platform_callback {
|
|||||||
* @cam_mclk: Pointer to camera functional clock structure.
|
* @cam_mclk: Pointer to camera functional clock structure.
|
||||||
* @csi2_fck: Pointer to camera CSI2 complexIO clock structure.
|
* @csi2_fck: Pointer to camera CSI2 complexIO clock structure.
|
||||||
* @l3_ick: Pointer to OMAP3 L3 bus interface clock.
|
* @l3_ick: Pointer to OMAP3 L3 bus interface clock.
|
||||||
|
* @xclks: External clocks provided by the ISP
|
||||||
* @irq: Currently attached ISP ISR callbacks information structure.
|
* @irq: Currently attached ISP ISR callbacks information structure.
|
||||||
* @isp_af: Pointer to current settings for ISP AutoFocus SCM.
|
* @isp_af: Pointer to current settings for ISP AutoFocus SCM.
|
||||||
* @isp_hist: Pointer to current settings for ISP Histogram SCM.
|
* @isp_hist: Pointer to current settings for ISP Histogram SCM.
|
||||||
@ -185,12 +199,12 @@ struct isp_device {
|
|||||||
int has_context;
|
int has_context;
|
||||||
int ref_count;
|
int ref_count;
|
||||||
unsigned int autoidle;
|
unsigned int autoidle;
|
||||||
u32 xclk_divisor[2]; /* Two clocks, a and b. */
|
|
||||||
#define ISP_CLK_CAM_ICK 0
|
#define ISP_CLK_CAM_ICK 0
|
||||||
#define ISP_CLK_CAM_MCLK 1
|
#define ISP_CLK_CAM_MCLK 1
|
||||||
#define ISP_CLK_CSI2_FCK 2
|
#define ISP_CLK_CSI2_FCK 2
|
||||||
#define ISP_CLK_L3_ICK 3
|
#define ISP_CLK_L3_ICK 3
|
||||||
struct clk *clock[4];
|
struct clk *clock[4];
|
||||||
|
struct isp_xclk xclks[2];
|
||||||
|
|
||||||
/* ISP modules */
|
/* ISP modules */
|
||||||
struct ispstat isp_af;
|
struct ispstat isp_af;
|
||||||
@ -209,8 +223,6 @@ struct isp_device {
|
|||||||
unsigned int subclk_resources;
|
unsigned int subclk_resources;
|
||||||
|
|
||||||
struct iommu_domain *domain;
|
struct iommu_domain *domain;
|
||||||
|
|
||||||
struct isp_platform_callback platform_cb;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define v4l2_dev_to_isp_device(dev) \
|
#define v4l2_dev_to_isp_device(dev) \
|
||||||
|
@ -29,10 +29,6 @@
|
|||||||
struct i2c_board_info;
|
struct i2c_board_info;
|
||||||
struct isp_device;
|
struct isp_device;
|
||||||
|
|
||||||
#define ISP_XCLK_NONE 0
|
|
||||||
#define ISP_XCLK_A 1
|
|
||||||
#define ISP_XCLK_B 2
|
|
||||||
|
|
||||||
enum isp_interface_type {
|
enum isp_interface_type {
|
||||||
ISP_INTERFACE_PARALLEL,
|
ISP_INTERFACE_PARALLEL,
|
||||||
ISP_INTERFACE_CSI2A_PHY2,
|
ISP_INTERFACE_CSI2A_PHY2,
|
||||||
@ -153,7 +149,13 @@ struct isp_v4l2_subdevs_group {
|
|||||||
} bus; /* gcc < 4.6.0 chokes on anonymous union initializers */
|
} bus; /* gcc < 4.6.0 chokes on anonymous union initializers */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct isp_platform_xclk {
|
||||||
|
const char *dev_id;
|
||||||
|
const char *con_id;
|
||||||
|
};
|
||||||
|
|
||||||
struct isp_platform_data {
|
struct isp_platform_data {
|
||||||
|
struct isp_platform_xclk xclks[2];
|
||||||
struct isp_v4l2_subdevs_group *subdevs;
|
struct isp_v4l2_subdevs_group *subdevs;
|
||||||
void (*set_constraints)(struct isp_device *isp, bool enable);
|
void (*set_constraints)(struct isp_device *isp, bool enable);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user