|
@@ -12,11 +12,12 @@
|
|
|
*
|
|
|
*/
|
|
|
#include <linux/kernel.h>
|
|
|
+#include <linux/mfd/syscon.h>
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/platform_device.h>
|
|
|
+#include <linux/regmap.h>
|
|
|
#include <linux/clk.h>
|
|
|
#include <linux/pm_runtime.h>
|
|
|
-#include <plat/map-base.h>
|
|
|
|
|
|
#include <drm/drmP.h>
|
|
|
#include <drm/exynos_drm.h>
|
|
@@ -76,6 +77,27 @@ enum fimc_wb {
|
|
|
FIMC_WB_B,
|
|
|
};
|
|
|
|
|
|
+enum {
|
|
|
+ FIMC_CLK_LCLK,
|
|
|
+ FIMC_CLK_GATE,
|
|
|
+ FIMC_CLK_WB_A,
|
|
|
+ FIMC_CLK_WB_B,
|
|
|
+ FIMC_CLK_MUX,
|
|
|
+ FIMC_CLK_PARENT,
|
|
|
+ FIMC_CLKS_MAX
|
|
|
+};
|
|
|
+
|
|
|
+static const char * const fimc_clock_names[] = {
|
|
|
+ [FIMC_CLK_LCLK] = "sclk_fimc",
|
|
|
+ [FIMC_CLK_GATE] = "fimc",
|
|
|
+ [FIMC_CLK_WB_A] = "pxl_async0",
|
|
|
+ [FIMC_CLK_WB_B] = "pxl_async1",
|
|
|
+ [FIMC_CLK_MUX] = "mux",
|
|
|
+ [FIMC_CLK_PARENT] = "parent",
|
|
|
+};
|
|
|
+
|
|
|
+#define FIMC_DEFAULT_LCLK_FREQUENCY 133000000UL
|
|
|
+
|
|
|
/*
|
|
|
* A structure of scaler.
|
|
|
*
|
|
@@ -118,15 +140,6 @@ struct fimc_capability {
|
|
|
u32 rl_h_rot;
|
|
|
};
|
|
|
|
|
|
-/*
|
|
|
- * A structure of fimc driver data.
|
|
|
- *
|
|
|
- * @parent_clk: name of parent clock.
|
|
|
- */
|
|
|
-struct fimc_driverdata {
|
|
|
- char *parent_clk;
|
|
|
-};
|
|
|
-
|
|
|
/*
|
|
|
* A structure of fimc context.
|
|
|
*
|
|
@@ -134,13 +147,10 @@ struct fimc_driverdata {
|
|
|
* @regs_res: register resources.
|
|
|
* @regs: memory mapped io registers.
|
|
|
* @lock: locking of operations.
|
|
|
- * @sclk_fimc_clk: fimc source clock.
|
|
|
- * @fimc_clk: fimc clock.
|
|
|
- * @wb_clk: writeback a clock.
|
|
|
- * @wb_b_clk: writeback b clock.
|
|
|
+ * @clocks: fimc clocks.
|
|
|
+ * @clk_frequency: LCLK clock frequency.
|
|
|
+ * @sysreg: handle to SYSREG block regmap.
|
|
|
* @sc: scaler infomations.
|
|
|
- * @odr: ordering of YUV.
|
|
|
- * @ver: fimc version.
|
|
|
* @pol: porarity of writeback.
|
|
|
* @id: fimc id.
|
|
|
* @irq: irq number.
|
|
@@ -151,12 +161,10 @@ struct fimc_context {
|
|
|
struct resource *regs_res;
|
|
|
void __iomem *regs;
|
|
|
struct mutex lock;
|
|
|
- struct clk *sclk_fimc_clk;
|
|
|
- struct clk *fimc_clk;
|
|
|
- struct clk *wb_clk;
|
|
|
- struct clk *wb_b_clk;
|
|
|
+ struct clk *clocks[FIMC_CLKS_MAX];
|
|
|
+ u32 clk_frequency;
|
|
|
+ struct regmap *sysreg;
|
|
|
struct fimc_scaler sc;
|
|
|
- struct fimc_driverdata *ddata;
|
|
|
struct exynos_drm_ipp_pol pol;
|
|
|
int id;
|
|
|
int irq;
|
|
@@ -200,17 +208,13 @@ static void fimc_sw_reset(struct fimc_context *ctx)
|
|
|
fimc_write(0x0, EXYNOS_CIFCNTSEQ);
|
|
|
}
|
|
|
|
|
|
-static void fimc_set_camblk_fimd0_wb(struct fimc_context *ctx)
|
|
|
+static int fimc_set_camblk_fimd0_wb(struct fimc_context *ctx)
|
|
|
{
|
|
|
- u32 camblk_cfg;
|
|
|
-
|
|
|
DRM_DEBUG_KMS("%s\n", __func__);
|
|
|
|
|
|
- camblk_cfg = readl(SYSREG_CAMERA_BLK);
|
|
|
- camblk_cfg &= ~(SYSREG_FIMD0WB_DEST_MASK);
|
|
|
- camblk_cfg |= ctx->id << (SYSREG_FIMD0WB_DEST_SHIFT);
|
|
|
-
|
|
|
- writel(camblk_cfg, SYSREG_CAMERA_BLK);
|
|
|
+ return regmap_update_bits(ctx->sysreg, SYSREG_CAMERA_BLK,
|
|
|
+ SYSREG_FIMD0WB_DEST_MASK,
|
|
|
+ ctx->id << SYSREG_FIMD0WB_DEST_SHIFT);
|
|
|
}
|
|
|
|
|
|
static void fimc_set_type_ctrl(struct fimc_context *ctx, enum fimc_wb wb)
|
|
@@ -1301,14 +1305,12 @@ static int fimc_clk_ctrl(struct fimc_context *ctx, bool enable)
|
|
|
DRM_DEBUG_KMS("%s:enable[%d]\n", __func__, enable);
|
|
|
|
|
|
if (enable) {
|
|
|
- clk_enable(ctx->sclk_fimc_clk);
|
|
|
- clk_enable(ctx->fimc_clk);
|
|
|
- clk_enable(ctx->wb_clk);
|
|
|
+ clk_prepare_enable(ctx->clocks[FIMC_CLK_GATE]);
|
|
|
+ clk_prepare_enable(ctx->clocks[FIMC_CLK_WB_A]);
|
|
|
ctx->suspended = false;
|
|
|
} else {
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- clk_disable(ctx->fimc_clk);
|
|
|
- clk_disable(ctx->wb_clk);
|
|
|
+ clk_disable_unprepare(ctx->clocks[FIMC_CLK_GATE]);
|
|
|
+ clk_disable_unprepare(ctx->clocks[FIMC_CLK_WB_A]);
|
|
|
ctx->suspended = true;
|
|
|
}
|
|
|
|
|
@@ -1613,7 +1615,11 @@ static int fimc_ippdrv_start(struct device *dev, enum drm_exynos_ipp_cmd cmd)
|
|
|
fimc_handle_lastend(ctx, true);
|
|
|
|
|
|
/* setup FIMD */
|
|
|
- fimc_set_camblk_fimd0_wb(ctx);
|
|
|
+ ret = fimc_set_camblk_fimd0_wb(ctx);
|
|
|
+ if (ret < 0) {
|
|
|
+ dev_err(dev, "camblk setup failed.\n");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
|
|
|
set_wb.enable = 1;
|
|
|
set_wb.refresh = property->refresh_rate;
|
|
@@ -1713,76 +1719,118 @@ static void fimc_ippdrv_stop(struct device *dev, enum drm_exynos_ipp_cmd cmd)
|
|
|
fimc_write(cfg, EXYNOS_CIGCTRL);
|
|
|
}
|
|
|
|
|
|
+static void fimc_put_clocks(struct fimc_context *ctx)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < FIMC_CLKS_MAX; i++) {
|
|
|
+ if (IS_ERR(ctx->clocks[i]))
|
|
|
+ continue;
|
|
|
+ clk_put(ctx->clocks[i]);
|
|
|
+ ctx->clocks[i] = ERR_PTR(-EINVAL);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int fimc_setup_clocks(struct fimc_context *ctx)
|
|
|
+{
|
|
|
+ struct device *fimc_dev = ctx->ippdrv.dev;
|
|
|
+ struct device *dev;
|
|
|
+ int ret, i;
|
|
|
+
|
|
|
+ for (i = 0; i < FIMC_CLKS_MAX; i++)
|
|
|
+ ctx->clocks[i] = ERR_PTR(-EINVAL);
|
|
|
+
|
|
|
+ for (i = 0; i < FIMC_CLKS_MAX; i++) {
|
|
|
+ if (i == FIMC_CLK_WB_A || i == FIMC_CLK_WB_B)
|
|
|
+ dev = fimc_dev->parent;
|
|
|
+ else
|
|
|
+ dev = fimc_dev;
|
|
|
+
|
|
|
+ ctx->clocks[i] = clk_get(dev, fimc_clock_names[i]);
|
|
|
+ if (IS_ERR(ctx->clocks[i])) {
|
|
|
+ if (i >= FIMC_CLK_MUX)
|
|
|
+ break;
|
|
|
+ ret = PTR_ERR(ctx->clocks[i]);
|
|
|
+ dev_err(fimc_dev, "failed to get clock: %s\n",
|
|
|
+ fimc_clock_names[i]);
|
|
|
+ goto e_clk_free;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Optional FIMC LCLK parent clock setting */
|
|
|
+ if (!IS_ERR(ctx->clocks[FIMC_CLK_PARENT])) {
|
|
|
+ ret = clk_set_parent(ctx->clocks[FIMC_CLK_MUX],
|
|
|
+ ctx->clocks[FIMC_CLK_PARENT]);
|
|
|
+ if (ret < 0) {
|
|
|
+ dev_err(fimc_dev, "failed to set parent.\n");
|
|
|
+ goto e_clk_free;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = clk_set_rate(ctx->clocks[FIMC_CLK_LCLK], ctx->clk_frequency);
|
|
|
+ if (ret < 0)
|
|
|
+ goto e_clk_free;
|
|
|
+
|
|
|
+ ret = clk_prepare_enable(ctx->clocks[FIMC_CLK_LCLK]);
|
|
|
+ if (!ret)
|
|
|
+ return ret;
|
|
|
+e_clk_free:
|
|
|
+ fimc_put_clocks(ctx);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int fimc_parse_dt(struct fimc_context *ctx)
|
|
|
+{
|
|
|
+ struct device_node *node = ctx->ippdrv.dev->of_node;
|
|
|
+
|
|
|
+ /* Handle only devices that support the LCD Writeback data path */
|
|
|
+ if (!of_property_read_bool(node, "samsung,lcd-wb"))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (of_property_read_u32(node, "clock-frequency",
|
|
|
+ &ctx->clk_frequency))
|
|
|
+ ctx->clk_frequency = FIMC_DEFAULT_LCLK_FREQUENCY;
|
|
|
+
|
|
|
+ ctx->id = of_alias_get_id(node, "fimc");
|
|
|
+
|
|
|
+ if (ctx->id < 0) {
|
|
|
+ dev_err(ctx->ippdrv.dev, "failed to get node alias id.\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int fimc_probe(struct platform_device *pdev)
|
|
|
{
|
|
|
struct device *dev = &pdev->dev;
|
|
|
struct fimc_context *ctx;
|
|
|
- struct clk *parent_clk;
|
|
|
struct resource *res;
|
|
|
struct exynos_drm_ippdrv *ippdrv;
|
|
|
- struct exynos_drm_fimc_pdata *pdata;
|
|
|
- struct fimc_driverdata *ddata;
|
|
|
int ret;
|
|
|
|
|
|
- pdata = pdev->dev.platform_data;
|
|
|
- if (!pdata) {
|
|
|
- dev_err(dev, "no platform data specified.\n");
|
|
|
- return -EINVAL;
|
|
|
+ if (!dev->of_node) {
|
|
|
+ dev_err(dev, "device tree node not found.\n");
|
|
|
+ return -ENODEV;
|
|
|
}
|
|
|
|
|
|
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
|
|
if (!ctx)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- ddata = (struct fimc_driverdata *)
|
|
|
- platform_get_device_id(pdev)->driver_data;
|
|
|
-
|
|
|
- /* clock control */
|
|
|
- ctx->sclk_fimc_clk = devm_clk_get(dev, "sclk_fimc");
|
|
|
- if (IS_ERR(ctx->sclk_fimc_clk)) {
|
|
|
- dev_err(dev, "failed to get src fimc clock.\n");
|
|
|
- return PTR_ERR(ctx->sclk_fimc_clk);
|
|
|
- }
|
|
|
- clk_enable(ctx->sclk_fimc_clk);
|
|
|
-
|
|
|
- ctx->fimc_clk = devm_clk_get(dev, "fimc");
|
|
|
- if (IS_ERR(ctx->fimc_clk)) {
|
|
|
- dev_err(dev, "failed to get fimc clock.\n");
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- return PTR_ERR(ctx->fimc_clk);
|
|
|
- }
|
|
|
-
|
|
|
- ctx->wb_clk = devm_clk_get(dev, "pxl_async0");
|
|
|
- if (IS_ERR(ctx->wb_clk)) {
|
|
|
- dev_err(dev, "failed to get writeback a clock.\n");
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- return PTR_ERR(ctx->wb_clk);
|
|
|
- }
|
|
|
-
|
|
|
- ctx->wb_b_clk = devm_clk_get(dev, "pxl_async1");
|
|
|
- if (IS_ERR(ctx->wb_b_clk)) {
|
|
|
- dev_err(dev, "failed to get writeback b clock.\n");
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- return PTR_ERR(ctx->wb_b_clk);
|
|
|
- }
|
|
|
+ ctx->ippdrv.dev = dev;
|
|
|
|
|
|
- parent_clk = devm_clk_get(dev, ddata->parent_clk);
|
|
|
-
|
|
|
- if (IS_ERR(parent_clk)) {
|
|
|
- dev_err(dev, "failed to get parent clock.\n");
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- return PTR_ERR(parent_clk);
|
|
|
- }
|
|
|
+ ret = fimc_parse_dt(ctx);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
|
|
|
- if (clk_set_parent(ctx->sclk_fimc_clk, parent_clk)) {
|
|
|
- dev_err(dev, "failed to set parent.\n");
|
|
|
- clk_disable(ctx->sclk_fimc_clk);
|
|
|
- return -EINVAL;
|
|
|
+ ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
|
+ "samsung,sysreg");
|
|
|
+ if (IS_ERR(ctx->sysreg)) {
|
|
|
+ dev_err(dev, "syscon regmap lookup failed.\n");
|
|
|
+ return PTR_ERR(ctx->sysreg);
|
|
|
}
|
|
|
|
|
|
- devm_clk_put(dev, parent_clk);
|
|
|
- clk_set_rate(ctx->sclk_fimc_clk, pdata->clk_rate);
|
|
|
-
|
|
|
/* resource memory */
|
|
|
ctx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
ctx->regs = devm_ioremap_resource(dev, ctx->regs_res);
|
|
@@ -1804,13 +1852,11 @@ static int fimc_probe(struct platform_device *pdev)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
- /* context initailization */
|
|
|
- ctx->id = pdev->id;
|
|
|
- ctx->pol = pdata->pol;
|
|
|
- ctx->ddata = ddata;
|
|
|
+ ret = fimc_setup_clocks(ctx);
|
|
|
+ if (ret < 0)
|
|
|
+ goto err_free_irq;
|
|
|
|
|
|
ippdrv = &ctx->ippdrv;
|
|
|
- ippdrv->dev = dev;
|
|
|
ippdrv->ops[EXYNOS_DRM_OPS_SRC] = &fimc_src_ops;
|
|
|
ippdrv->ops[EXYNOS_DRM_OPS_DST] = &fimc_dst_ops;
|
|
|
ippdrv->check_property = fimc_ippdrv_check_property;
|
|
@@ -1820,7 +1866,7 @@ static int fimc_probe(struct platform_device *pdev)
|
|
|
ret = fimc_init_prop_list(ippdrv);
|
|
|
if (ret < 0) {
|
|
|
dev_err(dev, "failed to init property list.\n");
|
|
|
- goto err_get_irq;
|
|
|
+ goto err_put_clk;
|
|
|
}
|
|
|
|
|
|
DRM_DEBUG_KMS("%s:id[%d]ippdrv[0x%x]\n", __func__, ctx->id,
|
|
@@ -1835,17 +1881,18 @@ static int fimc_probe(struct platform_device *pdev)
|
|
|
ret = exynos_drm_ippdrv_register(ippdrv);
|
|
|
if (ret < 0) {
|
|
|
dev_err(dev, "failed to register drm fimc device.\n");
|
|
|
- goto err_ippdrv_register;
|
|
|
+ goto err_pm_dis;
|
|
|
}
|
|
|
|
|
|
dev_info(&pdev->dev, "drm fimc registered successfully.\n");
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
-err_ippdrv_register:
|
|
|
- devm_kfree(dev, ippdrv->prop_list);
|
|
|
+err_pm_dis:
|
|
|
pm_runtime_disable(dev);
|
|
|
-err_get_irq:
|
|
|
+err_put_clk:
|
|
|
+ fimc_put_clocks(ctx);
|
|
|
+err_free_irq:
|
|
|
free_irq(ctx->irq, ctx);
|
|
|
|
|
|
return ret;
|
|
@@ -1857,10 +1904,10 @@ static int fimc_remove(struct platform_device *pdev)
|
|
|
struct fimc_context *ctx = get_fimc_context(dev);
|
|
|
struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv;
|
|
|
|
|
|
- devm_kfree(dev, ippdrv->prop_list);
|
|
|
exynos_drm_ippdrv_unregister(ippdrv);
|
|
|
mutex_destroy(&ctx->lock);
|
|
|
|
|
|
+ fimc_put_clocks(ctx);
|
|
|
pm_runtime_set_suspended(dev);
|
|
|
pm_runtime_disable(dev);
|
|
|
|
|
@@ -1915,36 +1962,22 @@ static int fimc_runtime_resume(struct device *dev)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
-static struct fimc_driverdata exynos4210_fimc_data = {
|
|
|
- .parent_clk = "mout_mpll",
|
|
|
-};
|
|
|
-
|
|
|
-static struct fimc_driverdata exynos4410_fimc_data = {
|
|
|
- .parent_clk = "mout_mpll_user",
|
|
|
-};
|
|
|
-
|
|
|
-static struct platform_device_id fimc_driver_ids[] = {
|
|
|
- {
|
|
|
- .name = "exynos4210-fimc",
|
|
|
- .driver_data = (unsigned long)&exynos4210_fimc_data,
|
|
|
- }, {
|
|
|
- .name = "exynos4412-fimc",
|
|
|
- .driver_data = (unsigned long)&exynos4410_fimc_data,
|
|
|
- },
|
|
|
- {},
|
|
|
-};
|
|
|
-MODULE_DEVICE_TABLE(platform, fimc_driver_ids);
|
|
|
-
|
|
|
static const struct dev_pm_ops fimc_pm_ops = {
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(fimc_suspend, fimc_resume)
|
|
|
SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL)
|
|
|
};
|
|
|
|
|
|
+static const struct of_device_id fimc_of_match[] = {
|
|
|
+ { .compatible = "samsung,exynos4210-fimc" },
|
|
|
+ { .compatible = "samsung,exynos4212-fimc" },
|
|
|
+ { },
|
|
|
+};
|
|
|
+
|
|
|
struct platform_driver fimc_driver = {
|
|
|
.probe = fimc_probe,
|
|
|
.remove = fimc_remove,
|
|
|
- .id_table = fimc_driver_ids,
|
|
|
.driver = {
|
|
|
+ .of_match_table = fimc_of_match,
|
|
|
.name = "exynos-drm-fimc",
|
|
|
.owner = THIS_MODULE,
|
|
|
.pm = &fimc_pm_ops,
|