diff options
| author | Clarence Ip <cip@codeaurora.org> | 2016-02-23 18:35:49 -0500 |
|---|---|---|
| committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-23 20:15:54 -0700 |
| commit | e43ffb6be17f41952fb656403aa8c08e1c9be267 (patch) | |
| tree | b0b836b91d3d18902078046f4e66db9636653d06 | |
| parent | 6c26c26834e1d3dd78e34287611797a284155b17 (diff) | |
msmfb: mdss: add mdp3 core driver
Add mdp3 core driver to the MDSS framework for msm8x10 support.
Change-Id: I859f0aa37fb888986a43bbfb08a329490c15d6bb
Signed-off-by: Xiaoming Zhou <zhoux@codeaurora.org>
[cip@codeaurora.org: Moved mdp3 file locations,
removed IRQF_DISABLED]
Signed-off-by: Clarence Ip <cip@codeaurora.org>
| -rw-r--r-- | drivers/video/fbdev/msm/Makefile | 3 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3.c | 917 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3.h | 120 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3_ctrl.c | 511 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3_ctrl.h | 37 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3_dma.c | 914 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3_dma.h | 336 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdp3_hwio.h | 216 |
8 files changed, 3054 insertions, 0 deletions
diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile index d4ec210c92b1..94a571c8e115 100644 --- a/drivers/video/fbdev/msm/Makefile +++ b/drivers/video/fbdev/msm/Makefile @@ -1,5 +1,8 @@ ccflags-y := -Idrivers/staging/android +mdss-mdp3-objs = mdp3.o mdp3_dma.o mdp3_ctrl.o +obj-$(CONFIG_FB_MSM_MDSS) += mdss-mdp3.o + mdss-mdp-objs := mdss_mdp.o mdss_mdp_ctl.o mdss_mdp_pipe.o mdss_mdp_util.o mdss-mdp-objs += mdss_mdp_pp.o mdss-mdp-objs += mdss_mdp_intf_video.o diff --git a/drivers/video/fbdev/msm/mdp3.c b/drivers/video/fbdev/msm/mdp3.c new file mode 100644 index 000000000000..8d5adcf7323f --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3.c @@ -0,0 +1,917 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iommu.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/memory_alloc.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/spinlock.h> +#include <linux/semaphore.h> +#include <linux/uaccess.h> + +#include <mach/board.h> +#include <mach/clk.h> +#include <mach/hardware.h> +#include <mach/msm_bus.h> +#include <mach/msm_bus_board.h> +#include <mach/iommu.h> +#include <mach/iommu_domains.h> +#include <mach/msm_memtypes.h> + +#include "mdp3.h" +#include "mdss_fb.h" +#include "mdp3_hwio.h" +#include "mdp3_ctrl.h" + +#define MDP_CORE_HW_VERSION 0x03030304 +struct mdp3_hw_resource *mdp3_res; + +#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val) \ + { \ + .src = MSM_BUS_MASTER_MDP_PORT0, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ab = (ab_val), \ + .ib = (ib_val), \ + } + +static struct msm_bus_vectors mdp_bus_vectors[] = { + MDP_BUS_VECTOR_ENTRY(0, 0), + MDP_BUS_VECTOR_ENTRY(SZ_128M, SZ_256M), + MDP_BUS_VECTOR_ENTRY(SZ_256M, SZ_512M), +}; + +static struct msm_bus_paths mdp_bus_usecases[ARRAY_SIZE(mdp_bus_vectors)]; + +static struct msm_bus_scale_pdata mdp_bus_scale_table = { + .usecase = mdp_bus_usecases, + .num_usecases = ARRAY_SIZE(mdp_bus_usecases), + .name = "mdp3", +}; + +struct mdp3_iommu_domain_map mdp3_iommu_domains[MDP3_IOMMU_DOMAIN_MAX] = { + [MDP3_IOMMU_DOMAIN] = { + .domain_type = MDP3_IOMMU_DOMAIN, + .client_name = "mdp_dma", + .partitions = { + { + .start = SZ_128K, + .size = SZ_1G - SZ_128K, + }, + }, + .npartitions = 1, + }, +}; + +struct mdp3_iommu_ctx_map mdp3_iommu_contexts[MDP3_IOMMU_CTX_MAX] = { + [MDP3_IOMMU_CTX_PPP_0] = { + .ctx_type = MDP3_IOMMU_CTX_PPP_0, + .domain = &mdp3_iommu_domains[MDP3_IOMMU_DOMAIN], + .ctx_name = "mdpe_0", + .attached = 0, + }, + [MDP3_IOMMU_CTX_PPP_1] = { + .ctx_type = MDP3_IOMMU_CTX_PPP_1, + .domain = &mdp3_iommu_domains[MDP3_IOMMU_DOMAIN], + .ctx_name = "mdpe_1", + .attached = 0, + }, + + [MDP3_IOMMU_CTX_DMA_0] = { + .ctx_type = MDP3_IOMMU_CTX_DMA_0, + .domain = &mdp3_iommu_domains[MDP3_IOMMU_DOMAIN], + .ctx_name = "mdps_0", + .attached = 0, + }, + + [MDP3_IOMMU_CTX_DMA_1] = { + .ctx_type = MDP3_IOMMU_CTX_DMA_1, + .domain = &mdp3_iommu_domains[MDP3_IOMMU_DOMAIN], + .ctx_name = "mdps_1", + .attached = 0, + }, +}; + +static irqreturn_t mdp3_irq_handler(int irq, void *ptr) +{ + int i = 0; + struct mdp3_hw_resource *mdata = (struct mdp3_hw_resource *)ptr; + u32 mdp_interrupt = MDP3_REG_READ(MDP3_REG_INTR_STATUS); + + MDP3_REG_WRITE(MDP3_REG_INTR_CLEAR, mdp_interrupt); + pr_debug("mdp3_irq_handler irq=%d\n", mdp_interrupt); + + spin_lock(&mdata->irq_lock); + mdp_interrupt &= mdata->irqMask; + + while (mdp_interrupt && i < MDP3_MAX_INTR) { + if ((mdp_interrupt & 0x1) && mdata->callbacks[i].cb) + mdata->callbacks[i].cb(i, mdata->callbacks[i].data); + mdp_interrupt = mdp_interrupt >> 1; + i++; + } + spin_unlock(&mdata->irq_lock); + + return IRQ_HANDLED; +} + +void mdp3_irq_enable(int type) +{ + unsigned long flag; + int irqEnabled = 0; + + pr_debug("mdp3_irq_enable type=%d\n", type); + spin_lock_irqsave(&mdp3_res->irq_lock, flag); + if (mdp3_res->irqMask & BIT(type)) { + pr_debug("interrupt %d already enabled\n", type); + spin_unlock_irqrestore(&mdp3_res->irq_lock, flag); + return; + } + irqEnabled = mdp3_res->irqMask; + mdp3_res->irqMask |= BIT(type); + MDP3_REG_WRITE(MDP3_REG_INTR_ENABLE, mdp3_res->irqMask); + if (!irqEnabled) + enable_irq(mdp3_res->irq); + spin_unlock_irqrestore(&mdp3_res->irq_lock, flag); +} + +void mdp3_irq_disable(int type) +{ + unsigned long flag; + + spin_lock_irqsave(&mdp3_res->irq_lock, flag); + if (mdp3_res->irqMask & BIT(type)) { + mdp3_res->irqMask &= ~BIT(type); + MDP3_REG_WRITE(MDP3_REG_INTR_ENABLE, mdp3_res->irqMask); + if (!mdp3_res->irqMask) + disable_irq(mdp3_res->irq); + } else { + pr_debug("interrupt %d not enabled\n", type); + } + spin_unlock_irqrestore(&mdp3_res->irq_lock, flag); +} + +void mdp3_irq_disable_nosync(int type) +{ + if (mdp3_res->irqMask & BIT(type)) { + mdp3_res->irqMask &= ~BIT(type); + MDP3_REG_WRITE(MDP3_REG_INTR_ENABLE, mdp3_res->irqMask); + if (!mdp3_res->irqMask) + disable_irq_nosync(mdp3_res->irq); + } else { + pr_debug("interrupt %d not enabled\n", type); + } +} + +int mdp3_set_intr_callback(u32 type, struct mdp3_intr_cb *cb) +{ + unsigned long flag; + + pr_debug("interrupt %d callback n", type); + spin_lock_irqsave(&mdp3_res->irq_lock, flag); + if (cb) + mdp3_res->callbacks[type] = *cb; + else + mdp3_res->callbacks[type].cb = NULL; + + spin_unlock_irqrestore(&mdp3_res->irq_lock, flag); + return 0; +} + +static int mdp3_bus_scale_register(void) +{ + if (!mdp3_res->bus_handle) { + struct msm_bus_scale_pdata *bus_pdata = &mdp_bus_scale_table; + int i; + + for (i = 0; i < bus_pdata->num_usecases; i++) { + mdp_bus_usecases[i].num_paths = 1; + mdp_bus_usecases[i].vectors = &mdp_bus_vectors[i]; + } + + mdp3_res->bus_handle = msm_bus_scale_register_client(bus_pdata); + if (!mdp3_res->bus_handle) { + pr_err("not able to get bus scale\n"); + return -ENOMEM; + } + pr_debug("register bus_hdl=%x\n", mdp3_res->bus_handle); + } + return 0; +} + +static void mdp3_bus_scale_unregister(void) +{ + pr_debug("unregister bus_handle=%x\n", mdp3_res->bus_handle); + + if (mdp3_res->bus_handle) + msm_bus_scale_unregister_client(mdp3_res->bus_handle); +} + +int mdp3_bus_scale_set_quota(int client, u64 ab_quota, u64 ib_quota) +{ + static int current_bus_idx; + int bus_idx; + int rc; + + if (mdp3_res->bus_handle < 1) { + pr_err("invalid bus handle %d\n", mdp3_res->bus_handle); + return -EINVAL; + } + + if ((ab_quota | ib_quota) == 0) { + bus_idx = 0; + } else { + int num_cases = mdp_bus_scale_table.num_usecases; + struct msm_bus_vectors *vect = NULL; + + bus_idx = (current_bus_idx % (num_cases - 1)) + 1; + + /* aligning to avoid performing updates for small changes */ + ab_quota = ALIGN(ab_quota, SZ_64M); + ib_quota = ALIGN(ib_quota, SZ_64M); + + vect = mdp_bus_scale_table.usecase[current_bus_idx].vectors; + if ((ab_quota == vect->ab) && (ib_quota == vect->ib)) { + pr_debug("skip bus scaling, no change in vectors\n"); + return 0; + } + + vect = mdp_bus_scale_table.usecase[bus_idx].vectors; + vect->ab = ab_quota; + vect->ib = ib_quota; + + pr_debug("bus scale idx=%d ab=%llu ib=%llu\n", bus_idx, + vect->ab, vect->ib); + } + current_bus_idx = bus_idx; + rc = msm_bus_scale_client_update_request(mdp3_res->bus_handle, bus_idx); + return rc; +} + +static int mdp3_clk_update(u32 clk_idx, u32 enable) +{ + int ret = -EINVAL; + struct clk *clk; + int count = 0; + + if (clk_idx >= MDP3_MAX_CLK || !mdp3_res->clocks[clk_idx]) + return -ENODEV; + + clk = mdp3_res->clocks[clk_idx]; + + if (enable) + mdp3_res->clock_ref_count[clk_idx]++; + else + mdp3_res->clock_ref_count[clk_idx]--; + + count = mdp3_res->clock_ref_count[clk_idx]; + if (count == 1) { + pr_debug("clk=%d en=%d\n", clk_idx, enable); + ret = clk_prepare_enable(clk); + } else if (count == 0) { + pr_debug("clk=%d disable\n", clk_idx); + clk_disable_unprepare(clk); + ret = 0; + } else if (count < 0) { + pr_err("clk=%d count=%d\n", clk_idx, count); + ret = -EINVAL; + } + return ret; +} + +int mdp3_vsync_clk_enable(int enable) +{ + int ret = 0; + + pr_debug("vsync clk enable=%d\n", enable); + mutex_lock(&mdp3_res->res_mutex); + mdp3_clk_update(MDP3_CLK_VSYNC, enable); + mutex_unlock(&mdp3_res->res_mutex); + return ret; +} + +int mdp3_clk_set_rate(int clk_type, unsigned long clk_rate) +{ + int ret = 0; + unsigned long rounded_rate; + struct clk *clk = mdp3_res->clocks[clk_type]; + + if (clk) { + mutex_lock(&mdp3_res->res_mutex); + rounded_rate = clk_round_rate(clk, clk_rate); + if (IS_ERR_VALUE(rounded_rate)) { + pr_err("unable to round rate err=%ld\n", rounded_rate); + mutex_unlock(&mdp3_res->res_mutex); + return -EINVAL; + } + if (rounded_rate != clk_get_rate(clk)) { + ret = clk_set_rate(clk, rounded_rate); + if (ret) + pr_err("clk_set_rate failed ret=%d\n", ret); + else + pr_debug("mdp clk rate=%lu\n", rounded_rate); + } + mutex_unlock(&mdp3_res->res_mutex); + } else { + pr_err("mdp src clk not setup properly\n"); + ret = -EINVAL; + } + return ret; +} + +unsigned long mdp3_get_clk_rate(u32 clk_idx) +{ + unsigned long clk_rate = 0; + struct clk *clk; + + if (clk_idx >= MDP3_MAX_CLK) + return -ENODEV; + + clk = mdp3_res->clocks[clk_idx]; + + if (clk) { + mutex_lock(&mdp3_res->res_mutex); + clk_rate = clk_get_rate(clk); + mutex_unlock(&mdp3_res->res_mutex); + } + return clk_rate; +} + +static int mdp3_clk_register(char *clk_name, int clk_idx) +{ + struct clk *tmp; + + if (clk_idx >= MDP3_MAX_CLK) { + pr_err("invalid clk index %d\n", clk_idx); + return -EINVAL; + } + + tmp = devm_clk_get(&mdp3_res->pdev->dev, clk_name); + if (IS_ERR(tmp)) { + pr_err("unable to get clk: %s\n", clk_name); + return PTR_ERR(tmp); + } + + mdp3_res->clocks[clk_idx] = tmp; + + return 0; +} + +static int mdp3_clk_setup(void) +{ + int rc; + + rc = mdp3_clk_register("iface_clk", MDP3_CLK_AHB); + if (rc) + return rc; + + rc = mdp3_clk_register("core_clk", MDP3_CLK_CORE); + if (rc) + return rc; + + rc = mdp3_clk_register("vsync_clk", MDP3_CLK_VSYNC); + if (rc) + return rc; + + rc = mdp3_clk_register("lcdc_clk", MDP3_CLK_LCDC); + if (rc) + return rc; + + return rc; +} + +static void mdp3_clk_remove(void) +{ + clk_put(mdp3_res->clocks[MDP3_CLK_AHB]); + clk_put(mdp3_res->clocks[MDP3_CLK_CORE]); + clk_put(mdp3_res->clocks[MDP3_CLK_VSYNC]); + clk_put(mdp3_res->clocks[MDP3_CLK_LCDC]); +} + +int mdp3_clk_enable(int enable) +{ + int rc; + + pr_debug("MDP CLKS %s\n", (enable ? "Enable" : "Disable")); + + mutex_lock(&mdp3_res->res_mutex); + rc = mdp3_clk_update(MDP3_CLK_AHB, enable); + rc |= mdp3_clk_update(MDP3_CLK_CORE, enable); + rc |= mdp3_clk_update(MDP3_CLK_VSYNC, enable); + mutex_unlock(&mdp3_res->res_mutex); + return rc; +} + +static int mdp3_irq_setup(void) +{ + int ret; + + ret = devm_request_irq(&mdp3_res->pdev->dev, + mdp3_res->irq, + mdp3_irq_handler, + 0x0, "MDP", mdp3_res); + if (ret) { + pr_err("mdp request_irq() failed!\n"); + return ret; + } + disable_irq(mdp3_res->irq); + return 0; +} + +static int mdp3_iommu_fault_handler(struct iommu_domain *domain, + struct device *dev, unsigned long iova, int flags, void *token) +{ + pr_err("MDP IOMMU page fault: iova 0x%lx\n", iova); + return 0; +} + +int mdp3_iommu_attach(int context) +{ + struct mdp3_iommu_ctx_map *context_map; + struct mdp3_iommu_domain_map *domain_map; + + if (context >= MDP3_IOMMU_CTX_MAX) + return -EINVAL; + + context_map = mdp3_res->iommu_contexts + context; + if (context_map->attached) { + pr_warn("mdp iommu already attached\n"); + return 0; + } + + domain_map = context_map->domain; + + iommu_attach_device(domain_map->domain, context_map->ctx); + + context_map->attached = true; + return 0; +} + +int mdp3_iommu_dettach(int context) +{ + struct mdp3_iommu_ctx_map *context_map; + struct mdp3_iommu_domain_map *domain_map; + + if (context >= MDP3_IOMMU_CTX_MAX) + return -EINVAL; + + context_map = mdp3_res->iommu_contexts + context; + if (!context_map->attached) { + pr_warn("mdp iommu not attached\n"); + return 0; + } + + domain_map = context_map->domain; + iommu_detach_device(domain_map->domain, context_map->ctx); + context_map->attached = false; + + return 0; +} + +int mdp3_iommu_domain_init(void) +{ + struct msm_iova_layout layout; + int i; + + if (mdp3_res->domains) { + pr_warn("iommu domain already initialized\n"); + return 0; + } + + for (i = 0; i < MDP3_IOMMU_DOMAIN_MAX; i++) { + int domain_idx; + layout.client_name = mdp3_iommu_domains[i].client_name; + layout.partitions = mdp3_iommu_domains[i].partitions; + layout.npartitions = mdp3_iommu_domains[i].npartitions; + layout.is_secure = false; + + domain_idx = msm_register_domain(&layout); + if (IS_ERR_VALUE(domain_idx)) + return -EINVAL; + + mdp3_iommu_domains[i].domain_idx = domain_idx; + mdp3_iommu_domains[i].domain = msm_get_iommu_domain(domain_idx); + if (!mdp3_iommu_domains[i].domain) { + pr_err("unable to get iommu domain(%d)\n", + domain_idx); + return -EINVAL; + } + iommu_set_fault_handler(mdp3_iommu_domains[i].domain, + mdp3_iommu_fault_handler, + NULL); + } + + mdp3_res->domains = mdp3_iommu_domains; + + return 0; +} + +int mdp3_iommu_context_init(void) +{ + int i; + + if (mdp3_res->iommu_contexts) { + pr_warn("iommu context already initialized\n"); + return 0; + } + + for (i = 0; i < MDP3_IOMMU_CTX_MAX; i++) { + mdp3_iommu_contexts[i].ctx = + msm_iommu_get_ctx(mdp3_iommu_contexts[i].ctx_name); + + if (!mdp3_iommu_contexts[i].ctx) { + pr_warn("unable to get iommu ctx(%s)\n", + mdp3_iommu_contexts[i].ctx_name); + return -EINVAL; + } + } + + mdp3_res->iommu_contexts = mdp3_iommu_contexts; + + return 0; +} + +int mdp3_iommu_init(void) +{ + int ret; + + ret = mdp3_iommu_domain_init(); + if (ret) { + pr_err("mdp3 iommu domain init fails\n"); + return ret; + } + + ret = mdp3_iommu_context_init(); + if (ret) { + pr_err("mdp3 iommu context init fails\n"); + return ret; + } + return ret; +} + +static int mdp3_check_version(void) +{ + int rc; + + rc = mdp3_clk_update(MDP3_CLK_AHB, 1); + if (rc) + return rc; + + mdp3_res->mdp_rev = MDP3_REG_READ(MDP3_REG_HW_VERSION); + + rc = mdp3_clk_update(MDP3_CLK_AHB, 0); + if (rc) + pr_err("fail to turn off the MDP3_CLK_AHB clk\n"); + + if (mdp3_res->mdp_rev != MDP_CORE_HW_VERSION) { + pr_err("mdp_hw_revision=%x mismatch\n", mdp3_res->mdp_rev); + rc = -ENODEV; + } + return rc; +} + +static int mdp3_hw_init(void) +{ + int i; + + for (i = MDP3_DMA_P; i < MDP3_DMA_MAX; i++) { + mdp3_res->dma[i].dma_sel = i; + mdp3_res->dma[i].capability = MDP3_DMA_CAP_ALL; + mdp3_res->dma[i].in_use = 0; + mdp3_res->dma[i].available = 1; + } + mdp3_res->dma[MDP3_DMA_S].capability = MDP3_DMA_CAP_DITHER; + mdp3_res->dma[MDP3_DMA_E].available = 0; + + for (i = MDP3_DMA_OUTPUT_SEL_AHB; i < MDP3_DMA_OUTPUT_SEL_MAX; i++) { + mdp3_res->intf[i].cfg.type = i; + mdp3_res->intf[i].active = 0; + mdp3_res->intf[i].in_use = 0; + mdp3_res->intf[i].available = 1; + } + mdp3_res->intf[MDP3_DMA_OUTPUT_SEL_AHB].available = 0; + mdp3_res->intf[MDP3_DMA_OUTPUT_SEL_LCDC].available = 0; + + return 0; +} + +static int mdp3_res_init(void) +{ + int rc = 0; + + rc = mdp3_irq_setup(); + if (rc) + return rc; + + rc = mdp3_clk_setup(); + if (rc) + return rc; + + mdp3_res->ion_client = msm_ion_client_create(-1, mdp3_res->pdev->name); + if (IS_ERR_OR_NULL(mdp3_res->ion_client)) { + pr_err("msm_ion_client_create() return error (%p)\n", + mdp3_res->ion_client); + mdp3_res->ion_client = NULL; + return -EINVAL; + } + + rc = mdp3_iommu_init(); + if (rc) + return rc; + + rc = mdp3_iommu_attach(MDP3_IOMMU_CTX_DMA_0); + if (rc) { + pr_err("fail to attach DMA-P context 0\n"); + return rc; + } + rc = mdp3_bus_scale_register(); + if (rc) { + pr_err("unable to register bus scaling\n"); + return rc; + } + + rc = mdp3_hw_init(); + + return rc; +} + +static int mdp3_parse_dt(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mdp_phys"); + if (!res) { + pr_err("unable to get MDP base address\n"); + return -EINVAL; + } + + mdp3_res->mdp_reg_size = resource_size(res); + mdp3_res->mdp_base = devm_ioremap(&pdev->dev, res->start, + mdp3_res->mdp_reg_size); + if (unlikely(!mdp3_res->mdp_base)) { + pr_err("unable to map MDP base\n"); + return -ENOMEM; + } + + pr_debug("MDP HW Base phy_Address=0x%x virt=0x%x\n", + (int) res->start, + (int) mdp3_res->mdp_base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + pr_err("unable to get MDSS irq\n"); + return -EINVAL; + } + mdp3_res->irq = res->start; + + return 0; +} + +static int mdp3_init(struct msm_fb_data_type *mfd) +{ + return mdp3_ctrl_init(mfd); +} + +u32 mdp3_fb_stride(u32 fb_index, u32 xres, int bpp) +{ + /* + * The adreno GPU hardware requires that the pitch be aligned to + * 32 pixels for color buffers, so for the cases where the GPU + * is writing directly to fb0, the framebuffer pitch + * also needs to be 32 pixel aligned + */ + + if (fb_index == 0) + return ALIGN(xres, 32) * bpp; + else + return xres * bpp; +} + +/* + * physical contiguous memory should be allocated in mdss_fb, and SMMU + * virtual address mapping can be done in the MDP h/w specific code. It + * should have a reference count, if none is current mapped, the SMMU context + * can bedetached, thus allowing power saving in SMMU. + */ +static int mdp3_fbmem_alloc(struct msm_fb_data_type *mfd) +{ + int dom; + void *virt = NULL; + unsigned long phys = 0; + size_t size; + u32 yres = mfd->fbi->var.yres_virtual; + + size = PAGE_ALIGN(mfd->fbi->fix.line_length * yres); + + if (mfd->index == 0) { + virt = allocate_contiguous_memory(size, MEMTYPE_EBI1, SZ_1M, 0); + if (!virt) { + pr_err("unable to alloc fbmem size=%u\n", size); + return -ENOMEM; + } + phys = memory_pool_node_paddr(virt); + dom = (mdp3_res->domains + MDP3_IOMMU_DOMAIN)->domain_idx; + msm_iommu_map_contig_buffer(phys, dom, 0, size, SZ_4K, 0, + &mfd->iova); + + pr_debug("allocating %u bytes at %p (%lx phys) for fb %d\n", + size, virt, phys, mfd->index); + } else { + size = 0; + } + + mfd->fbi->screen_base = virt; + mfd->fbi->fix.smem_start = phys; + mfd->fbi->fix.smem_len = size; + return 0; +} + +struct mdp3_dma *mdp3_get_dma_pipe(int capability) +{ + int i; + + for (i = MDP3_DMA_P; i < MDP3_DMA_MAX; i++) { + if (!mdp3_res->dma[i].in_use && mdp3_res->dma[i].available && + mdp3_res->dma[i].capability & capability) { + mdp3_res->dma[i].in_use = true; + return &mdp3_res->dma[i]; + } + } + return NULL; +} + +struct mdp3_intf *mdp3_get_display_intf(int type) +{ + int i; + + for (i = MDP3_DMA_OUTPUT_SEL_AHB; i < MDP3_DMA_OUTPUT_SEL_MAX; i++) { + if (!mdp3_res->intf[i].in_use && mdp3_res->intf[i].available && + mdp3_res->intf[i].cfg.type == type) { + mdp3_res->intf[i].in_use = true; + return &mdp3_res->intf[i]; + } + } + return NULL; +} + +static int mdp3_probe(struct platform_device *pdev) +{ + int rc; + static struct msm_mdp_interface mdp3_interface = { + .init_fnc = mdp3_init, + .fb_mem_alloc_fnc = mdp3_fbmem_alloc, + .fb_stride = mdp3_fb_stride, + }; + + if (!pdev->dev.of_node) { + pr_err("MDP driver only supports device tree probe\n"); + return -ENOTSUPP; + } + + if (mdp3_res) { + pr_err("MDP already initialized\n"); + return -EINVAL; + } + + mdp3_res = devm_kzalloc(&pdev->dev, sizeof(struct mdp3_hw_resource), + GFP_KERNEL); + if (mdp3_res == NULL) + return -ENOMEM; + + pdev->id = 0; + mdp3_res->pdev = pdev; + mutex_init(&mdp3_res->res_mutex); + spin_lock_init(&mdp3_res->irq_lock); + platform_set_drvdata(pdev, mdp3_res); + + rc = mdp3_parse_dt(pdev); + if (rc) + goto probe_done; + + rc = mdp3_res_init(); + if (rc) { + pr_err("unable to initialize mdp3 resources\n"); + goto probe_done; + } + + rc = mdp3_check_version(); + if (rc) { + pr_err("mdp3 check version failed\n"); + goto probe_done; + } + + rc = mdss_fb_register_mdp_instance(&mdp3_interface); + if (rc) + pr_err("unable to register mdp instance\n"); + +probe_done: + if (IS_ERR_VALUE(rc)) { + devm_kfree(&pdev->dev, mdp3_res); + mdp3_res = NULL; + } + + return rc; +} + +static int mdp3_suspend_sub(struct mdp3_hw_resource *mdata) +{ + return 0; +} + +static int mdp3_resume_sub(struct mdp3_hw_resource *mdata) +{ + return 0; +} + +static int mdp3_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mdp3_hw_resource *mdata = platform_get_drvdata(pdev); + + if (!mdata) + return -ENODEV; + + pr_debug("display suspend\n"); + + return mdp3_suspend_sub(mdata); +} + +static int mdp3_resume(struct platform_device *pdev) +{ + struct mdp3_hw_resource *mdata = platform_get_drvdata(pdev); + + if (!mdata) + return -ENODEV; + + pr_debug("display resume\n"); + + return mdp3_resume_sub(mdata); +} + +static int mdp3_remove(struct platform_device *pdev) +{ + struct mdp3_hw_resource *mdata = platform_get_drvdata(pdev); + + if (!mdata) + return -ENODEV; + pm_runtime_disable(&pdev->dev); + mdp3_bus_scale_unregister(); + mdp3_clk_remove(); + return 0; +} + +static const struct of_device_id mdp3_dt_match[] = { + { .compatible = "qcom,mdss_mdp3",}, + {} +}; +MODULE_DEVICE_TABLE(of, mdp3_dt_match); +EXPORT_COMPAT("qcom,mdss_mdp3"); + +static struct platform_driver mdp3_driver = { + .probe = mdp3_probe, + .remove = mdp3_remove, + .suspend = mdp3_suspend, + .resume = mdp3_resume, + .shutdown = NULL, + .driver = { + .name = "mdp3", + .of_match_table = mdp3_dt_match, + }, +}; + +static int __init mdp3_driver_init(void) +{ + int ret; + + ret = platform_driver_register(&mdp3_driver); + if (ret) { + pr_err("register mdp3 driver failed!\n"); + return ret; + } + + return 0; +} + +module_init(mdp3_driver_init); diff --git a/drivers/video/fbdev/msm/mdp3.h b/drivers/video/fbdev/msm/mdp3.h new file mode 100644 index 000000000000..3fbc30b5cab9 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3.h @@ -0,0 +1,120 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (C) 2007 Google Incorporated + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef MDP3_H +#define MDP3_H + +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include <mach/iommu_domains.h> + +#include "mdp3_dma.h" + +enum { + MDP3_CLK_AHB, + MDP3_CLK_CORE, + MDP3_CLK_VSYNC, + MDP3_CLK_LCDC, + MDP3_MAX_CLK +}; + +enum { + MDP3_IOMMU_DOMAIN, + MDP3_IOMMU_DOMAIN_MAX +}; + +enum { + MDP3_IOMMU_CTX_PPP_0, + MDP3_IOMMU_CTX_PPP_1, + MDP3_IOMMU_CTX_DMA_0, + MDP3_IOMMU_CTX_DMA_1, + MDP3_IOMMU_CTX_MAX +}; + +enum { + MDP3_BW_CLIENT_DMA_P, + MDP3_BW_CLIENT_DMA_S, + MDP3_BW_CLIENT_DMA_E, + MDP3_BW_CLIENT_PPP, +}; + +struct mdp3_iommu_domain_map { + u32 domain_type; + char *client_name; + struct msm_iova_partition partitions[1]; + int npartitions; + int domain_idx; + struct iommu_domain *domain; +}; + +struct mdp3_iommu_ctx_map { + u32 ctx_type; + struct mdp3_iommu_domain_map *domain; + char *ctx_name; + struct device *ctx; + int attached; +}; + +#define MDP3_MAX_INTR 28 + +struct mdp3_intr_cb { + void (*cb)(int type, void *); + void *data; +}; + +struct mdp3_hw_resource { + struct platform_device *pdev; + u32 mdp_rev; + + struct mutex res_mutex; + + struct clk *clocks[MDP3_MAX_CLK]; + int clock_ref_count[MDP3_MAX_CLK]; + + char __iomem *mdp_base; + size_t mdp_reg_size; + + u32 irq; + u32 bus_handle; + + struct ion_client *ion_client; + struct mdp3_iommu_domain_map *domains; + struct mdp3_iommu_ctx_map *iommu_contexts; + + struct mdp3_dma dma[MDP3_DMA_MAX]; + struct mdp3_intf intf[MDP3_DMA_OUTPUT_SEL_MAX]; + + spinlock_t irq_lock; + u32 irqMask; + struct mdp3_intr_cb callbacks[MDP3_MAX_INTR]; +}; + +extern struct mdp3_hw_resource *mdp3_res; + +struct mdp3_dma *mdp3_get_dma_pipe(int capability); +struct mdp3_intf *mdp3_get_display_intf(int type); +void mdp3_irq_enable(int type); +void mdp3_irq_disable(int type); +void mdp3_irq_disable_nosync(int type); +int mdp3_set_intr_callback(u32 type, struct mdp3_intr_cb *cb); +int mdp3_clk_set_rate(int clk_type, unsigned long clk_rate); +int mdp3_clk_enable(int enable); +int mdp3_bus_scale_set_quota(int client, u64 ab_quota, u64 ib_quota); + +#define MDP3_REG_WRITE(addr, val) writel_relaxed(val, mdp3_res->mdp_base + addr) +#define MDP3_REG_READ(addr) readl_relaxed(mdp3_res->mdp_base + addr) + +#endif /* MDP3_H */ diff --git a/drivers/video/fbdev/msm/mdp3_ctrl.c b/drivers/video/fbdev/msm/mdp3_ctrl.c new file mode 100644 index 000000000000..e07c0a4fa2c6 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3_ctrl.c @@ -0,0 +1,511 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/module.h> +#include <linux/uaccess.h> + +#include "mdp3_ctrl.h" +#include "mdp3.h" + +#define MDP_VSYNC_CLK_RATE 19200000 +#define VSYNC_PERIOD 16 + +void vsync_notify_handler(void *arg) +{ + struct mdp3_session_data *session = (struct mdp3_session_data *)session; + complete(&session->vsync_comp); +} + +static int mdp3_ctrl_vsync_enable(struct msm_fb_data_type *mfd, int enable) +{ + struct mdp3_session_data *mdp3_session; + struct mdp3_vsync_notification vsync_client; + + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + if (!mdp3_session || !mdp3_session->panel || !mdp3_session->dma || + !mdp3_session->intf) + return -ENODEV; + + vsync_client.handler = vsync_notify_handler; + vsync_client.arg = mdp3_session; + + mutex_lock(&mdp3_session->lock); + if (!mdp3_session->status) { + pr_debug("fb%d is not on yet", mfd->index); + mutex_unlock(&mdp3_session->lock); + return -EINVAL; + } + + mdp3_session->dma->vsync_enable(mdp3_session->dma, &vsync_client); + mutex_unlock(&mdp3_session->lock); + return 0; +} + +static ssize_t mdp3_vsync_show_event(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fbi = dev_get_drvdata(dev); + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; + struct mdp3_session_data *mdp3_session = NULL; + u64 vsync_ticks; + ktime_t vsync_time; + int rc; + + if (!mfd || !mfd->mdp.private1) + return 0; + + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + + rc = wait_for_completion_interruptible_timeout( + &mdp3_session->vsync_comp, + msecs_to_jiffies(VSYNC_PERIOD * 5)); + if (rc <= 0) { + pr_warn("vsync wait on fb%d interrupted (%d)\n", + mfd->index, rc); + return -EBUSY; + } + + vsync_time = mdp3_session->dma->get_vsync_time(mdp3_session->dma); + vsync_ticks = ktime_to_ns(vsync_time); + + pr_debug("fb%d vsync=%llu", mfd->index, vsync_ticks); + rc = snprintf(buf, PAGE_SIZE, "VSYNC=%llu", vsync_ticks); + return rc; +} + +static DEVICE_ATTR(vsync_event, S_IRUGO, mdp3_vsync_show_event, NULL); + +static struct attribute *vsync_fs_attrs[] = { + &dev_attr_vsync_event.attr, + NULL, +}; + +static struct attribute_group vsync_fs_attr_group = { + .attrs = vsync_fs_attrs, +}; + +static int mdp3_ctrl_res_req_dma(struct msm_fb_data_type *mfd, int status) +{ + int rc = 0; + if (status) { + struct mdss_panel_info *panel_info = mfd->panel_info; + int ab = 0; + int ib = 0; + unsigned long core_clk = 0; + int vtotal = 0; + ab = panel_info->xres * panel_info->yres * 4; + ab *= panel_info->mipi.frame_rate; + ib = (ab * 3) / 2; + vtotal = panel_info->lcdc.v_back_porch + + panel_info->lcdc.v_front_porch + + panel_info->lcdc.v_pulse_width + + panel_info->yres; + core_clk = panel_info->xres * panel_info->yres; + core_clk *= panel_info->mipi.frame_rate; + core_clk = (core_clk / panel_info->yres) * vtotal; + mdp3_clk_set_rate(MDP3_CLK_CORE, core_clk); + mdp3_clk_set_rate(MDP3_CLK_VSYNC, MDP_VSYNC_CLK_RATE); + + rc = mdp3_clk_enable(true); + if (rc) + return rc; + + mdp3_bus_scale_set_quota(MDP3_BW_CLIENT_DMA_P, ab, ib); + } else { + rc = mdp3_clk_enable(false); + rc |= mdp3_bus_scale_set_quota(MDP3_BW_CLIENT_DMA_P, 0, 0); + } + return rc; +} + +static int mdp3_ctrl_get_intf_type(struct msm_fb_data_type *mfd) +{ + int type; + switch (mfd->panel.type) { + case MIPI_VIDEO_PANEL: + type = MDP3_DMA_OUTPUT_SEL_DSI_VIDEO; + break; + case MIPI_CMD_PANEL: + type = MDP3_DMA_OUTPUT_SEL_DSI_CMD; + break; + case LCDC_PANEL: + type = MDP3_DMA_OUTPUT_SEL_LCDC; + break; + default: + type = MDP3_DMA_OUTPUT_SEL_MAX; + } + return type; +} + +static int mdp3_ctrl_get_source_format(struct msm_fb_data_type *mfd) +{ + int format; + switch (mfd->fb_imgType) { + case MDP_RGB_565: + format = MDP3_DMA_IBUF_FORMAT_RGB565; + break; + case MDP_RGB_888: + format = MDP3_DMA_IBUF_FORMAT_RGB888; + break; + case MDP_ARGB_8888: + case MDP_RGBA_8888: + format = MDP3_DMA_IBUF_FORMAT_XRGB8888; + break; + default: + format = MDP3_DMA_IBUF_FORMAT_UNDEFINED; + } + return format; +} + +static int mdp3_ctrl_get_pack_pattern(struct msm_fb_data_type *mfd) +{ + int packPattern = MDP3_DMA_OUTPUT_PACK_PATTERN_RGB; + if (mfd->fb_imgType == MDP_RGBA_8888) + packPattern = MDP3_DMA_OUTPUT_PACK_PATTERN_BGR; + return packPattern; +} + +static int mdp3_ctrl_intf_init(struct msm_fb_data_type *mfd, + struct mdp3_intf *intf) +{ + int rc; + struct mdp3_intf_cfg cfg; + struct mdp3_video_intf_cfg *video = &cfg.video; + struct mdss_panel_info *p = mfd->panel_info; + int h_back_porch = p->lcdc.h_back_porch; + int h_front_porch = p->lcdc.h_front_porch; + int w = p->xres; + int v_back_porch = p->lcdc.v_back_porch; + int v_front_porch = p->lcdc.v_front_porch; + int h = p->yres; + int h_sync_skew = p->lcdc.hsync_skew; + int h_pulse_width = p->lcdc.h_pulse_width; + int v_pulse_width = p->lcdc.v_pulse_width; + int hsync_period = h_front_porch + h_back_porch + w + h_pulse_width; + int vsync_period = v_front_porch + v_back_porch + h + v_pulse_width; + vsync_period *= hsync_period; + + cfg.type = mdp3_ctrl_get_intf_type(mfd); + if (cfg.type == MDP3_DMA_OUTPUT_SEL_DSI_VIDEO || + cfg.type == MDP3_DMA_OUTPUT_SEL_LCDC) { + video->hsync_period = hsync_period; + video->hsync_pulse_width = h_pulse_width; + video->vsync_period = vsync_period; + video->vsync_pulse_width = v_pulse_width * hsync_period; + video->display_start_x = h_back_porch + h_pulse_width; + video->display_end_x = hsync_period - h_front_porch - 1; + video->display_start_y = + (v_back_porch + v_pulse_width) * hsync_period; + video->display_end_y = + vsync_period - v_front_porch * hsync_period - 1; + video->active_start_x = video->display_start_x; + video->active_end_x = video->display_end_x; + video->active_h_enable = true; + video->active_start_y = video->display_start_y; + video->active_end_y = video->display_end_y; + video->active_v_enable = true; + video->hsync_skew = h_sync_skew; + video->hsync_polarity = 1; + video->vsync_polarity = 1; + video->de_polarity = 1; + } else if (cfg.type == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + cfg.dsi_cmd.primary_dsi_cmd_id = 0; + cfg.dsi_cmd.secondary_dsi_cmd_id = 1; + cfg.dsi_cmd.dsi_cmd_tg_intf_sel = 0; + } else + return -EINVAL; + rc = mdp3_intf_init(intf, &cfg); + return rc; +} + +static int mdp3_ctrl_dma_init(struct msm_fb_data_type *mfd, + struct mdp3_dma *dma) +{ + int rc; + struct mdss_panel_info *panel_info = mfd->panel_info; + struct fb_info *fbi = mfd->fbi; + struct fb_fix_screeninfo *fix; + struct fb_var_screeninfo *var; + struct mdp3_dma_output_config outputConfig; + struct mdp3_dma_source sourceConfig; + + fix = &fbi->fix; + var = &fbi->var; + + sourceConfig.format = mdp3_ctrl_get_source_format(mfd); + sourceConfig.width = panel_info->xres; + sourceConfig.height = panel_info->yres; + sourceConfig.x = 0; + sourceConfig.y = 0; + sourceConfig.stride = fix->line_length; + sourceConfig.buf = (void *)mfd->iova; + + outputConfig.dither_en = 0; + outputConfig.out_sel = mdp3_ctrl_get_intf_type(mfd); + outputConfig.bit_mask_polarity = 0; + outputConfig.color_components_flip = 0; + outputConfig.pack_pattern = mdp3_ctrl_get_pack_pattern(mfd); + outputConfig.pack_align = MDP3_DMA_OUTPUT_PACK_ALIGN_LSB; + outputConfig.color_comp_out_bits = (MDP3_DMA_OUTPUT_COMP_BITS_8 << 4) | + (MDP3_DMA_OUTPUT_COMP_BITS_8 << 2)| + MDP3_DMA_OUTPUT_COMP_BITS_8; + + rc = mdp3_dma_init(dma, &sourceConfig, &outputConfig); + return rc; +} + +static int mdp3_ctrl_on(struct msm_fb_data_type *mfd) +{ + int rc = 0; + struct mdp3_session_data *mdp3_session; + struct mdss_panel_data *panel; + + pr_debug("mdp3_ctrl_on\n"); + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + if (!mdp3_session || !mdp3_session->panel || !mdp3_session->dma || + !mdp3_session->intf) { + pr_err("mdp3_ctrl_on no device"); + return -ENODEV; + } + mutex_lock(&mdp3_session->lock); + if (mdp3_session->status) { + pr_info("fb%d is on already", mfd->index); + goto on_error; + } + + rc = mdp3_ctrl_res_req_dma(mfd, 1); + if (rc) { + pr_err("resource request for dma on failed\n"); + goto on_error; + } + + rc = mdp3_ctrl_dma_init(mfd, mdp3_session->dma); + if (rc) { + pr_err("dma init failed\n"); + goto on_error; + } + + rc = mdp3_ctrl_intf_init(mfd, mdp3_session->intf); + if (rc) { + pr_err("display interface init failed\n"); + goto on_error; + } + + panel = mdp3_session->panel; + + if (panel->event_handler) + rc = panel->event_handler(panel, MDSS_EVENT_PANEL_ON, NULL); + + if (rc) { + pr_err("fail to turn on the panel\n"); + goto on_error; + } + + rc = mdp3_session->dma->start(mdp3_session->dma, mdp3_session->intf); + if (rc) { + pr_err("fail to start the MDP display interface\n"); + goto on_error; + } + +on_error: + if (!rc) + mdp3_session->status = 1; + + mutex_unlock(&mdp3_session->lock); + return rc; +} + +static int mdp3_ctrl_off(struct msm_fb_data_type *mfd) +{ + int rc = 0; + struct mdp3_session_data *mdp3_session; + struct mdss_panel_data *panel; + + pr_debug("mdp3_ctrl_off\n"); + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + if (!mdp3_session || !mdp3_session->panel || !mdp3_session->dma || + !mdp3_session->intf) { + pr_err("mdp3_ctrl_on no device"); + return -ENODEV; + } + + mutex_lock(&mdp3_session->lock); + + if (!mdp3_session->status) { + pr_info("fb%d is off already", mfd->index); + goto off_error; + } + + panel = mdp3_session->panel; + if (panel->event_handler) + rc = panel->event_handler(panel, MDSS_EVENT_PANEL_OFF, NULL); + + if (rc) + pr_err("fail to turn off the panel\n"); + + rc = mdp3_session->dma->stop(mdp3_session->dma, mdp3_session->intf); + + if (rc) + pr_err("fail to stop the MDP3 dma\n"); + + rc = mdp3_ctrl_res_req_dma(mfd, 0); + if (rc) + pr_err("resource release for dma on failed\n"); + +off_error: + mdp3_session->status = 0; + + mutex_unlock(&mdp3_session->lock); + return 0; +} + +static void mdp3_ctrl_pan_display(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi; + struct mdp3_session_data *mdp3_session; + u32 offset; + int bpp; + + pr_debug("mdp3_ctrl_pan_display\n"); + if (!mfd || !mfd->mdp.private1) + return; + + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + if (!mdp3_session || !mdp3_session->dma) + return; + + if (!mdp3_session->status) { + pr_err("mdp3_ctrl_pan_display, display off!\n"); + return; + } + + mutex_lock(&mdp3_session->lock); + fbi = mfd->fbi; + + bpp = fbi->var.bits_per_pixel / 8; + offset = fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + if (offset > fbi->fix.smem_len) { + pr_err("invalid fb offset=%u total length=%u\n", + offset, fbi->fix.smem_len); + goto pan_error; + } + + mdp3_session->dma->update(mdp3_session->dma, + (void *)mfd->iova + offset); +pan_error: + mutex_unlock(&mdp3_session->lock); +} + +static int mdp3_ctrl_ioctl_handler(struct msm_fb_data_type *mfd, + u32 cmd, void __user *argp) +{ + int rc = -EINVAL; + struct mdp3_session_data *mdp3_session; + int val; + + pr_debug("mdp3_ctrl_ioctl_handler\n"); + + mdp3_session = (struct mdp3_session_data *)mfd->mdp.private1; + if (!mdp3_session) + return -ENODEV; + + if (!mdp3_session->status) { + pr_err("mdp3_ctrl_ioctl_handler, display off!\n"); + return -EINVAL; + } + + switch (cmd) { + case MSMFB_VSYNC_CTRL: + case MSMFB_OVERLAY_VSYNC_CTRL: + if (!copy_from_user(&val, argp, sizeof(val))) { + rc = mdp3_ctrl_vsync_enable(mfd, val); + if (!val) + init_completion(&mdp3_session->vsync_comp); + } else { + pr_err("MSMFB_OVERLAY_VSYNC_CTRL failed\n"); + rc = -EFAULT; + } + break; + default: + break; + } + + return rc; +} + +int mdp3_ctrl_init(struct msm_fb_data_type *mfd) +{ + struct device *dev = mfd->fbi->dev; + struct msm_mdp_interface *mdp3_interface = &mfd->mdp; + struct mdp3_session_data *mdp3_session = NULL; + u32 intf_type = MDP3_DMA_OUTPUT_SEL_DSI_VIDEO; + int rc; + + pr_debug("mdp3_ctrl_init\n"); + mdp3_interface->on_fnc = mdp3_ctrl_on; + mdp3_interface->off_fnc = mdp3_ctrl_off; + mdp3_interface->do_histogram = NULL; + mdp3_interface->cursor_update = NULL; + mdp3_interface->dma_fnc = mdp3_ctrl_pan_display; + mdp3_interface->ioctl_handler = mdp3_ctrl_ioctl_handler; + mdp3_interface->kickoff_fnc = NULL; + + mdp3_session = kmalloc(sizeof(struct mdp3_session_data), GFP_KERNEL); + if (!mdp3_session) { + pr_err("fail to allocate mdp3 private data structure"); + return -ENOMEM; + } + memset(mdp3_session, 0, sizeof(struct mdp3_session_data)); + mutex_init(&mdp3_session->lock); + init_completion(&mdp3_session->vsync_comp); + mdp3_session->dma = mdp3_get_dma_pipe(MDP3_DMA_CAP_ALL); + if (!mdp3_session->dma) { + rc = -ENODEV; + goto init_done; + } + + intf_type = mdp3_ctrl_get_intf_type(mfd); + mdp3_session->intf = mdp3_get_display_intf(intf_type); + if (!mdp3_session->intf) { + rc = -ENODEV; + goto init_done; + } + + mdp3_session->panel = dev_get_platdata(&mfd->pdev->dev); + mdp3_session->status = 0; + + mfd->mdp.private1 = mdp3_session; + + rc = sysfs_create_group(&dev->kobj, &vsync_fs_attr_group); + if (rc) { + pr_err("vsync sysfs group creation failed, ret=%d\n", rc); + goto init_done; + } + + kobject_uevent(&dev->kobj, KOBJ_ADD); + pr_debug("vsync kobject_uevent(KOBJ_ADD)\n"); + +init_done: + if (IS_ERR_VALUE(rc)) + kfree(mdp3_session); + + return rc; +} diff --git a/drivers/video/fbdev/msm/mdp3_ctrl.h b/drivers/video/fbdev/msm/mdp3_ctrl.h new file mode 100644 index 000000000000..d42ece70f209 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3_ctrl.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDP3_CTRL_H +#define MDP3_CTRL_H + +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/completion.h> + +#include "mdp3_dma.h" +#include "mdss_fb.h" +#include "mdss_panel.h" + +struct mdp3_session_data { + struct mutex lock; + int status; + struct mdp3_dma *dma; + struct mdss_panel_data *panel; + struct mdp3_intf *intf; + struct msm_fb_data_type *mfd; + struct completion vsync_comp; +}; + +int mdp3_ctrl_init(struct msm_fb_data_type *mfd); + +#endif /* MDP3_CTRL_H */ diff --git a/drivers/video/fbdev/msm/mdp3_dma.c b/drivers/video/fbdev/msm/mdp3_dma.c new file mode 100644 index 000000000000..69e3d7e5e8cf --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3_dma.c @@ -0,0 +1,914 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/bitops.h> +#include <linux/iopoll.h> + +#include "mdp3.h" +#include "mdp3_dma.h" +#include "mdp3_hwio.h" + +#define DMA_STOP_POLL_SLEEP_US 1000 +#define DMA_STOP_POLL_TIMEOUT_US 16000 + +static ktime_t mdp3_get_vsync_time(struct mdp3_dma *dma) +{ + unsigned long flag; + ktime_t time; + + spin_lock_irqsave(&dma->dma_lock, flag); + time = dma->vsync_time; + spin_unlock_irqrestore(&dma->dma_lock, flag); + return time; +} + +static void mdp3_vsync_intr_handler(int type, void *arg) +{ + struct mdp3_dma *dma = (struct mdp3_dma *)arg; + struct mdp3_vsync_notification vsync_client; + + pr_debug("mdp3_vsync_intr_handler\n"); + spin_lock(&dma->dma_lock); + vsync_client = dma->vsync_client; + if (!vsync_client.handler) + dma->cb_type &= ~MDP3_DMA_CALLBACK_TYPE_VSYNC; + dma->vsync_time = ktime_get(); + complete(&dma->vsync_comp); + if (vsync_client.handler) + vsync_client.handler(vsync_client.arg); + spin_unlock(&dma->dma_lock); + + if (!vsync_client.handler) + mdp3_irq_disable_nosync(type); +} + +static void mdp3_dma_done_intr_handler(int type, void *arg) +{ + struct mdp3_dma *dma = (struct mdp3_dma *)arg; + + pr_debug("mdp3_dma_done_intr_handler\n"); + spin_lock(&dma->dma_lock); + dma->busy = false; + dma->cb_type &= ~MDP3_DMA_CALLBACK_TYPE_DMA_DONE; + spin_unlock(&dma->dma_lock); + complete(&dma->dma_comp); + mdp3_irq_disable_nosync(type); +} + +void mdp3_dma_callback_enable(struct mdp3_dma *dma, int type) +{ + int irq_bit; + unsigned long flag; + + pr_debug("mdp3_dma_callback_enable type=%d\n", type); + + spin_lock_irqsave(&dma->dma_lock, flag); + if (dma->cb_type & type) { + spin_unlock_irqrestore(&dma->dma_lock, flag); + return; + } else { + dma->cb_type |= type; + spin_unlock_irqrestore(&dma->dma_lock, flag); + } + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_VIDEO || + dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_LCDC) { + if (type & MDP3_DMA_CALLBACK_TYPE_VSYNC) + mdp3_irq_enable(MDP3_INTR_LCDC_START_OF_FRAME); + } else if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + if (type & MDP3_DMA_CALLBACK_TYPE_VSYNC) { + irq_bit = MDP3_INTR_SYNC_PRIMARY_LINE; + irq_bit += dma->dma_sel; + mdp3_irq_enable(irq_bit); + } + + if (type & MDP3_DMA_CALLBACK_TYPE_DMA_DONE) { + irq_bit = MDP3_INTR_DMA_P_DONE; + if (dma->dma_sel == MDP3_DMA_S) + irq_bit = MDP3_INTR_DMA_S_DONE; + mdp3_irq_enable(irq_bit); + } + } else { + pr_err("mdp3_dma_callback_enable not supported interface\n"); + } +} + +void mdp3_dma_callback_disable(struct mdp3_dma *dma, int type) +{ + int irq_bit; + unsigned long flag; + + pr_debug("mdp3_dma_callback_disable type=%d\n", type); + + spin_lock_irqsave(&dma->dma_lock, flag); + if ((dma->cb_type & type) == 0) { + spin_unlock_irqrestore(&dma->dma_lock, flag); + return; + } else { + dma->cb_type &= ~type; + spin_unlock_irqrestore(&dma->dma_lock, flag); + } + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_VIDEO || + dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_LCDC) { + if (type & MDP3_DMA_CALLBACK_TYPE_VSYNC) + mdp3_irq_disable(MDP3_INTR_LCDC_START_OF_FRAME); + } else if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + if (type & MDP3_DMA_CALLBACK_TYPE_VSYNC) { + irq_bit = MDP3_INTR_SYNC_PRIMARY_LINE; + irq_bit += dma->dma_sel; + mdp3_irq_disable(irq_bit); + } + + if (type & MDP3_DMA_CALLBACK_TYPE_DMA_DONE) { + irq_bit = MDP3_INTR_DMA_P_DONE; + if (dma->dma_sel == MDP3_DMA_S) + irq_bit = MDP3_INTR_DMA_S_DONE; + mdp3_irq_disable(irq_bit); + } + } +} + +static int mdp3_dma_callback_setup(struct mdp3_dma *dma) +{ + int rc; + struct mdp3_intr_cb vsync_cb = { + .cb = mdp3_vsync_intr_handler, + .data = dma, + }; + + struct mdp3_intr_cb dma_cb = { + .cb = mdp3_dma_done_intr_handler, + .data = dma, + }; + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_VIDEO || + dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_LCDC) + rc = mdp3_set_intr_callback(MDP3_INTR_LCDC_START_OF_FRAME, + &vsync_cb); + else if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + int irq_bit = MDP3_INTR_SYNC_PRIMARY_LINE; + irq_bit += dma->dma_sel; + rc = mdp3_set_intr_callback(irq_bit, &vsync_cb); + irq_bit = MDP3_INTR_DMA_P_DONE; + if (dma->dma_sel == MDP3_DMA_S) + irq_bit = MDP3_INTR_DMA_S_DONE; + rc |= mdp3_set_intr_callback(irq_bit, &dma_cb); + } else { + pr_err("mdp3_dma_callback_setup not suppported interface\n"); + rc = -ENODEV; + } + return rc; +} + +static void mdp3_dma_vsync_enable(struct mdp3_dma *dma, + struct mdp3_vsync_notification *vsync_client) +{ + unsigned long flag; + int updated = 0; + int cb_type = MDP3_DMA_CALLBACK_TYPE_VSYNC; + + pr_debug("mdp3_dma_vsync_enable\n"); + + spin_lock_irqsave(&dma->dma_lock, flag); + if (vsync_client) { + if (dma->vsync_client.handler != vsync_client->handler) { + dma->vsync_client = *vsync_client; + updated = 1; + } + } else { + if (!dma->vsync_client.handler) { + dma->vsync_client.handler = NULL; + dma->vsync_client.arg = NULL; + updated = 1; + } + } + spin_unlock_irqrestore(&dma->dma_lock, flag); + + if (updated) { + if (vsync_client && vsync_client->handler) + mdp3_dma_callback_enable(dma, cb_type); + else + mdp3_dma_callback_disable(dma, cb_type); + } +} + +static int mdp3_dmap_config(struct mdp3_dma *dma, + struct mdp3_dma_source *source_config, + struct mdp3_dma_output_config *output_config) +{ + u32 dma_p_cfg_reg, dma_p_size, dma_p_out_xy; + + dma_p_cfg_reg = source_config->format << 25; + if (output_config->dither_en) + dma_p_cfg_reg |= BIT(24); + dma_p_cfg_reg |= output_config->out_sel << 19; + dma_p_cfg_reg |= output_config->bit_mask_polarity << 18; + dma_p_cfg_reg |= output_config->color_components_flip << 14; + dma_p_cfg_reg |= output_config->pack_pattern << 8; + dma_p_cfg_reg |= output_config->pack_align << 7; + dma_p_cfg_reg |= output_config->color_comp_out_bits; + + dma_p_size = source_config->width | (source_config->height << 16); + dma_p_out_xy = source_config->x | (source_config->y << 16); + + MDP3_REG_WRITE(MDP3_REG_DMA_P_CONFIG, dma_p_cfg_reg); + MDP3_REG_WRITE(MDP3_REG_DMA_P_SIZE, dma_p_size); + MDP3_REG_WRITE(MDP3_REG_DMA_P_IBUF_ADDR, (u32)source_config->buf); + MDP3_REG_WRITE(MDP3_REG_DMA_P_IBUF_Y_STRIDE, source_config->stride); + MDP3_REG_WRITE(MDP3_REG_DMA_P_OUT_XY, dma_p_out_xy); + + /* + * NOTE: MDP_DMA_P_FETCH_CFG: max_burst_size need to use value 4, not + * the default 16 for MDP hang issue workaround + */ + MDP3_REG_WRITE(MDP3_REG_DMA_P_FETCH_CFG, 0x10); + MDP3_REG_WRITE(MDP3_REG_PRIMARY_RD_PTR_IRQ, 0x10); + + dma->source_config = *source_config; + dma->output_config = *output_config; + + mdp3_dma_callback_setup(dma); + return 0; +} + +static int mdp3_dmas_config(struct mdp3_dma *dma, + struct mdp3_dma_source *source_config, + struct mdp3_dma_output_config *output_config) +{ + u32 dma_s_cfg_reg, dma_s_size, dma_s_out_xy; + + dma_s_cfg_reg = source_config->format << 25; + if (output_config->dither_en) + dma_s_cfg_reg |= BIT(24); + dma_s_cfg_reg |= output_config->out_sel << 19; + dma_s_cfg_reg |= output_config->bit_mask_polarity << 18; + dma_s_cfg_reg |= output_config->color_components_flip << 14; + dma_s_cfg_reg |= output_config->pack_pattern << 8; + dma_s_cfg_reg |= output_config->pack_align << 7; + dma_s_cfg_reg |= output_config->color_comp_out_bits; + + dma_s_size = source_config->width | (source_config->height << 16); + dma_s_out_xy = source_config->x | (source_config->y << 16); + + MDP3_REG_WRITE(MDP3_REG_DMA_S_CONFIG, dma_s_cfg_reg); + MDP3_REG_WRITE(MDP3_REG_DMA_S_SIZE, dma_s_size); + MDP3_REG_WRITE(MDP3_REG_DMA_S_IBUF_ADDR, (u32)source_config->buf); + MDP3_REG_WRITE(MDP3_REG_DMA_S_IBUF_Y_STRIDE, source_config->stride); + MDP3_REG_WRITE(MDP3_REG_DMA_S_OUT_XY, dma_s_out_xy); + + MDP3_REG_WRITE(MDP3_REG_SECONDARY_RD_PTR_IRQ, 0x10); + + dma->source_config = *source_config; + dma->output_config = *output_config; + + mdp3_dma_callback_setup(dma); + return 0; +} + +static int mdp3_dmap_cursor_config(struct mdp3_dma *dma, + struct mdp3_dma_cursor *cursor) +{ + u32 cursor_size, cursor_pos, blend_param, trans_mask; + + cursor_size = cursor->width | (cursor->height << 16); + cursor_pos = cursor->x | (cursor->y << 16); + trans_mask = 0; + if (cursor->blend_config.mode == MDP3_DMA_CURSOR_BLEND_CONSTANT_ALPHA) { + blend_param = cursor->blend_config.constant_alpha << 24; + } else if (cursor->blend_config.mode == + MDP3_DMA_CURSOR_BLEND_COLOR_KEYING) { + blend_param = cursor->blend_config.transparent_color; + trans_mask = cursor->blend_config.transparency_mask; + } else { + blend_param = 0; + } + + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_FORMAT, cursor->format); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_SIZE, cursor_size); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_BUF_ADDR, (u32)cursor->buf); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_POS, cursor_pos); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_BLEND_CONFIG, + cursor->blend_config.mode); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_BLEND_PARAM, blend_param); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_BLEND_TRANS_MASK, trans_mask); + dma->cursor = *cursor; + return 0; +} + +static int mdp3_dmap_ccs_config(struct mdp3_dma *dma, + struct mdp3_dma_color_correct_config *config, + struct mdp3_dma_ccs *ccs, + struct mdp3_dma_lut *lut) +{ + int i; + u32 addr, cc_config, color; + + cc_config = config->lut_enable; + if (config->ccs_enable) + cc_config |= BIT(3); + cc_config |= config->lut_position << 4; + cc_config |= config->ccs_sel << 5; + cc_config |= config->pre_bias_sel << 6; + cc_config |= config->post_bias_sel << 7; + cc_config |= config->pre_limit_sel << 8; + cc_config |= config->post_limit_sel << 9; + cc_config |= config->lut_sel << 10; + + MDP3_REG_WRITE(MDP3_REG_DMA_P_COLOR_CORRECT_CONFIG, cc_config); + + if (config->ccs_enable && ccs) { + if (ccs->mv1) { + addr = MDP3_REG_DMA_P_CSC_MV1; + for (i = 0; i < 9; i++) { + MDP3_REG_WRITE(addr, ccs->mv1[i]); + addr += 4; + } + } + + if (ccs->mv2) { + addr = MDP3_REG_DMA_P_CSC_MV2; + for (i = 0; i < 9; i++) { + MDP3_REG_WRITE(addr, ccs->mv2[i]); + addr += 4; + } + } + + if (ccs->pre_bv1) { + addr = MDP3_REG_DMA_P_CSC_PRE_BV1; + for (i = 0; i < 3; i++) { + MDP3_REG_WRITE(addr, ccs->pre_bv1[i]); + addr += 4; + } + } + + if (ccs->pre_bv2) { + addr = MDP3_REG_DMA_P_CSC_PRE_BV2; + for (i = 0; i < 3; i++) { + MDP3_REG_WRITE(addr, ccs->pre_bv2[i]); + addr += 4; + } + } + + if (ccs->post_bv1) { + addr = MDP3_REG_DMA_P_CSC_POST_BV1; + for (i = 0; i < 3; i++) { + MDP3_REG_WRITE(addr, ccs->post_bv1[i]); + addr += 4; + } + } + + if (ccs->post_bv2) { + addr = MDP3_REG_DMA_P_CSC_POST_BV2; + for (i = 0; i < 3; i++) { + MDP3_REG_WRITE(addr, ccs->post_bv2[i]); + addr += 4; + } + } + + if (ccs->pre_lv1) { + addr = MDP3_REG_DMA_P_CSC_PRE_LV1; + for (i = 0; i < 6; i++) { + MDP3_REG_WRITE(addr, ccs->pre_lv1[i]); + addr += 4; + } + } + + if (ccs->pre_lv2) { + addr = MDP3_REG_DMA_P_CSC_PRE_LV2; + for (i = 0; i < 6; i++) { + MDP3_REG_WRITE(addr, ccs->pre_lv2[i]); + addr += 4; + } + } + + if (ccs->post_lv1) { + addr = MDP3_REG_DMA_P_CSC_POST_LV1; + for (i = 0; i < 6; i++) { + MDP3_REG_WRITE(addr, ccs->post_lv1[i]); + addr += 4; + } + } + + if (ccs->post_lv2) { + addr = MDP3_REG_DMA_P_CSC_POST_LV2; + for (i = 0; i < 6; i++) { + MDP3_REG_WRITE(addr, ccs->post_lv2[i]); + addr += 4; + } + } + } + + if (config->lut_enable && lut) { + if (lut->color0_lut1 && lut->color1_lut1 && lut->color2_lut1) { + addr = MDP3_REG_DMA_P_CSC_LUT1; + for (i = 0; i < 256; i++) { + color = lut->color0_lut1[i]; + color |= lut->color1_lut1[i] << 8; + color |= lut->color2_lut1[i] << 16; + MDP3_REG_WRITE(addr, color); + addr += 4; + } + } + + if (lut->color0_lut2 && lut->color1_lut2 && lut->color2_lut2) { + addr = MDP3_REG_DMA_P_CSC_LUT2; + for (i = 0; i < 256; i++) { + color = lut->color0_lut2[i]; + color |= lut->color1_lut2[i] << 8; + color |= lut->color2_lut2[i] << 16; + MDP3_REG_WRITE(addr, color); + addr += 4; + } + } + } + + dma->ccs_config = *config; + return 0; +} + +static int mdp3_dmap_histo_config(struct mdp3_dma *dma, + struct mdp3_dma_histogram_config *histo_config) +{ + u32 hist_bit_mask, hist_control; + + if (histo_config->bit_mask_polarity) + hist_bit_mask = BIT(31); + hist_bit_mask |= histo_config->bit_mask; + + if (histo_config->auto_clear_en) + hist_control = BIT(0); + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_FRAME_CNT, + histo_config->frame_count); + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_BIT_MASK, hist_bit_mask); + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_CONTROL, hist_control); + return 0; +} + +static int mdp3_dmap_update(struct mdp3_dma *dma, void *buf) +{ + int wait_for_dma_done = 0; + unsigned long flag; + int cb_type = MDP3_DMA_CALLBACK_TYPE_VSYNC; + + pr_debug("mdp3_dmap_update\n"); + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + cb_type |= MDP3_DMA_CALLBACK_TYPE_DMA_DONE; + spin_lock_irqsave(&dma->dma_lock, flag); + if (dma->busy) + wait_for_dma_done = 1; + spin_unlock_irqrestore(&dma->dma_lock, flag); + + if (wait_for_dma_done) + wait_for_completion_killable(&dma->dma_comp); + } + + spin_lock_irqsave(&dma->dma_lock, flag); + MDP3_REG_WRITE(MDP3_REG_DMA_P_IBUF_ADDR, (u32)buf); + dma->source_config.buf = buf; + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + MDP3_REG_WRITE(MDP3_REG_DMA_P_START, 1); + dma->busy = true; + } + wmb(); + init_completion(&dma->vsync_comp); + spin_unlock_irqrestore(&dma->dma_lock, flag); + + mdp3_dma_callback_enable(dma, cb_type); + pr_debug("mdp3_dmap_update wait for vsync_comp in\n"); + wait_for_completion_killable(&dma->vsync_comp); + pr_debug("mdp3_dmap_update wait for vsync_comp out\n"); + return 0; +} + +static int mdp3_dmas_update(struct mdp3_dma *dma, void *buf) +{ + int wait_for_dma_done = 0; + unsigned long flag; + int cb_type = MDP3_DMA_CALLBACK_TYPE_VSYNC; + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + cb_type |= MDP3_DMA_CALLBACK_TYPE_DMA_DONE; + spin_lock_irqsave(&dma->dma_lock, flag); + if (dma->busy) + wait_for_dma_done = 1; + spin_unlock_irqrestore(&dma->dma_lock, flag); + + if (wait_for_dma_done) + wait_for_completion_killable(&dma->dma_comp); + } + + spin_lock_irqsave(&dma->dma_lock, flag); + MDP3_REG_WRITE(MDP3_REG_DMA_S_IBUF_ADDR, (u32)buf); + dma->source_config.buf = buf; + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + MDP3_REG_WRITE(MDP3_REG_DMA_S_START, 1); + dma->busy = true; + } + wmb(); + init_completion(&dma->vsync_comp); + spin_unlock_irqrestore(&dma->dma_lock, flag); + + mdp3_dma_callback_enable(dma, cb_type); + wait_for_completion_killable(&dma->vsync_comp); + return 0; +} + +static int mdp3_dmap_cursor_update(struct mdp3_dma *dma, int x, int y) +{ + u32 cursor_pos; + + cursor_pos = x | (y << 16); + MDP3_REG_WRITE(MDP3_REG_DMA_P_CURSOR_POS, cursor_pos); + dma->cursor.x = x; + dma->cursor.y = y; + return 0; +} + +static int mdp3_dmap_histo_get(struct mdp3_dma *dma, + struct mdp3_dma_histogram_data *data) +{ + int i; + u32 addr, extra; + + addr = MDP3_REG_DMA_P_HIST_R_DATA; + for (i = 0; i < 32; i++) { + data->r_data[i] = MDP3_REG_READ(addr); + addr += 4; + } + + addr = MDP3_REG_DMA_P_HIST_G_DATA; + for (i = 0; i < 32; i++) { + data->g_data[i] = MDP3_REG_READ(addr); + addr += 4; + } + + addr = MDP3_REG_DMA_P_HIST_B_DATA; + for (i = 0; i < 32; i++) { + data->b_data[i] = MDP3_REG_READ(addr); + addr += 4; + } + + extra = MDP3_REG_READ(MDP3_REG_DMA_P_HIST_EXTRA_INFO_0); + data->r_min_value = (extra & 0x1F0000) >> 16; + data->r_max_value = (extra & 0x1F000000) >> 24; + extra = MDP3_REG_READ(MDP3_REG_DMA_P_HIST_EXTRA_INFO_1); + data->g_min_value = extra & 0x1F; + data->g_max_value = (extra & 0x1F00) >> 8; + data->b_min_value = (extra & 0x1F0000) >> 16; + data->b_max_value = (extra & 0x1F000000) >> 24; + return 0; +} + +static int mdp3_dmap_histo_op(struct mdp3_dma *dma, u32 op) +{ + switch (op) { + case MDP3_DMA_HISTO_OP_START: + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_START, 1); + break; + case MDP3_DMA_HISTO_OP_STOP: + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_STOP_REQ, 1); + break; + case MDP3_DMA_HISTO_OP_CANCEL: + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_CANCEL_REQ, 1); + break; + case MDP3_DMA_HISTO_OP_RESET: + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_RESET_SEQ_START, 1); + break; + default: + return -EINVAL; + } + return 0; +} + +static int mdp3_dmap_histo_intr_status(struct mdp3_dma *dma, int *status) +{ + *status = MDP3_REG_READ(MDP3_REG_DMA_P_HIST_INTR_STATUS); + return 0; +} + +static int mdp3_dmap_histo_intr_enable(struct mdp3_dma *dma, u32 mask) +{ + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_INTR_ENABLE, mask); + return 0; +} + +static int mdp3_dmap_histo_intr_clear(struct mdp3_dma *dma, u32 mask) +{ + MDP3_REG_WRITE(MDP3_REG_DMA_P_HIST_INTR_CLEAR, mask); + return 0; +} + +static int mdp3_dma_start(struct mdp3_dma *dma, struct mdp3_intf *intf) +{ + unsigned long flag; + int cb_type = MDP3_DMA_CALLBACK_TYPE_VSYNC; + u32 dma_start_offset = MDP3_REG_DMA_P_START; + + if (dma->dma_sel == MDP3_DMA_P) + dma_start_offset = MDP3_REG_DMA_P_START; + else if (dma->dma_sel == MDP3_DMA_S) + dma_start_offset = MDP3_REG_DMA_S_START; + else + return -EINVAL; + + spin_lock_irqsave(&dma->dma_lock, flag); + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD) { + cb_type |= MDP3_DMA_CALLBACK_TYPE_DMA_DONE; + MDP3_REG_WRITE(dma_start_offset, 1); + dma->busy = true; + } + + intf->start(intf); + wmb(); + init_completion(&dma->vsync_comp); + spin_unlock_irqrestore(&dma->dma_lock, flag); + + mdp3_dma_callback_enable(dma, cb_type); + pr_debug("mdp3_dma_start wait for vsync_comp in\n"); + wait_for_completion_killable(&dma->vsync_comp); + pr_debug("mdp3_dma_start wait for vsync_comp out\n"); + return 0; +} + +static int mdp3_dma_stop(struct mdp3_dma *dma, struct mdp3_intf *intf) +{ + int ret = 0; + u32 status, display_status_bit; + + if (dma->dma_sel == MDP3_DMA_P) + display_status_bit = BIT(6); + else if (dma->dma_sel == MDP3_DMA_S) + display_status_bit = BIT(7); + else + return -EINVAL; + + if (dma->output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_VIDEO) + display_status_bit |= BIT(11); + + intf->stop(intf); + ret = readl_poll_timeout((mdp3_res->mdp_base + MDP3_REG_DISPLAY_STATUS), + status, + ((status & display_status_bit) == 0), + DMA_STOP_POLL_SLEEP_US, + DMA_STOP_POLL_TIMEOUT_US); + + mdp3_dma_callback_disable(dma, MDP3_DMA_CALLBACK_TYPE_VSYNC | + MDP3_DMA_CALLBACK_TYPE_DMA_DONE); + + dma->busy = false; + return ret; +} + +int mdp3_dma_init(struct mdp3_dma *dma, + struct mdp3_dma_source *source_config, + struct mdp3_dma_output_config *output_config) +{ + int ret = 0; + + pr_debug("mdp3_dma_init\n"); + switch (dma->dma_sel) { + case MDP3_DMA_P: + dma->busy = 0; + + ret = mdp3_dmap_config(dma, source_config, output_config); + if (ret < 0) + return ret; + + dma->config_cursor = mdp3_dmap_cursor_config; + dma->config_ccs = mdp3_dmap_ccs_config; + dma->config_histo = mdp3_dmap_histo_config; + dma->update = mdp3_dmap_update; + dma->update_cursor = mdp3_dmap_cursor_update; + dma->get_histo = mdp3_dmap_histo_get; + dma->histo_op = mdp3_dmap_histo_op; + dma->histo_intr_status = mdp3_dmap_histo_intr_status; + dma->histo_intr_enable = mdp3_dmap_histo_intr_enable; + dma->histo_intr_clear = mdp3_dmap_histo_intr_clear; + dma->vsync_enable = mdp3_dma_vsync_enable; + dma->get_vsync_time = mdp3_get_vsync_time; + dma->start = mdp3_dma_start; + dma->stop = mdp3_dma_stop; + break; + case MDP3_DMA_S: + dma->busy = 0; + ret = mdp3_dmas_config(dma, source_config, output_config); + if (ret < 0) + return ret; + + dma->config_cursor = NULL; + dma->config_ccs = NULL; + dma->config_histo = NULL; + dma->update = mdp3_dmas_update; + dma->update_cursor = NULL; + dma->get_histo = NULL; + dma->histo_op = NULL; + dma->histo_intr_status = NULL; + dma->histo_intr_enable = NULL; + dma->histo_intr_clear = NULL; + dma->vsync_enable = mdp3_dma_vsync_enable; + dma->get_vsync_time = mdp3_get_vsync_time; + dma->start = mdp3_dma_start; + dma->stop = mdp3_dma_stop; + break; + case MDP3_DMA_E: + default: + ret = -ENODEV; + break; + } + + spin_lock_init(&dma->dma_lock); + init_completion(&dma->vsync_comp); + init_completion(&dma->dma_comp); + dma->cb_type = 0; + dma->vsync_client.handler = NULL; + dma->vsync_client.arg = NULL; + + memset(&dma->cursor, 0, sizeof(dma->cursor)); + memset(&dma->ccs_config, 0, sizeof(dma->ccs_config)); + memset(&dma->histogram_config, 0, sizeof(dma->histogram_config)); + + return ret; +} + +int lcdc_config(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg) +{ + u32 temp; + struct mdp3_video_intf_cfg *v = &cfg->video; + temp = v->hsync_pulse_width | (v->hsync_period << 16); + MDP3_REG_WRITE(MDP3_REG_LCDC_HSYNC_CTL, temp); + MDP3_REG_WRITE(MDP3_REG_LCDC_VSYNC_PERIOD, v->vsync_period); + MDP3_REG_WRITE(MDP3_REG_LCDC_VSYNC_PULSE_WIDTH, v->vsync_pulse_width); + temp = v->display_start_x | (v->display_end_x << 16); + MDP3_REG_WRITE(MDP3_REG_LCDC_DISPLAY_HCTL, temp); + MDP3_REG_WRITE(MDP3_REG_LCDC_DISPLAY_V_START, v->display_start_y); + MDP3_REG_WRITE(MDP3_REG_LCDC_DISPLAY_V_END, v->display_end_y); + temp = v->active_start_x | (v->active_end_x); + if (v->active_h_enable) + temp |= BIT(31); + MDP3_REG_WRITE(MDP3_REG_LCDC_ACTIVE_HCTL, temp); + MDP3_REG_WRITE(MDP3_REG_LCDC_ACTIVE_V_START, v->active_start_y); + MDP3_REG_WRITE(MDP3_REG_LCDC_ACTIVE_V_END, v->active_end_y); + MDP3_REG_WRITE(MDP3_REG_LCDC_HSYNC_SKEW, v->hsync_skew); + temp = 0; + if (!v->hsync_polarity) + temp = BIT(0); + if (!v->vsync_polarity) + temp = BIT(1); + if (!v->de_polarity) + temp = BIT(2); + MDP3_REG_WRITE(MDP3_REG_LCDC_CTL_POLARITY, temp); + + return 0; +} + +int lcdc_start(struct mdp3_intf *intf) +{ + MDP3_REG_WRITE(MDP3_REG_LCDC_EN, BIT(0)); + wmb(); + intf->active = true; + return 0; +} + +int lcdc_stop(struct mdp3_intf *intf) +{ + MDP3_REG_WRITE(MDP3_REG_LCDC_EN, 0); + wmb(); + intf->active = false; + return 0; +} + +int dsi_video_config(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg) +{ + u32 temp; + struct mdp3_video_intf_cfg *v = &cfg->video; + + pr_debug("dsi_video_config\n"); + + temp = v->hsync_pulse_width | (v->hsync_period << 16); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_HSYNC_CTL, temp); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_VSYNC_PERIOD, v->vsync_period); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_VSYNC_PULSE_WIDTH, + v->vsync_pulse_width); + temp = v->display_start_x | (v->display_end_x << 16); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_DISPLAY_HCTL, temp); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_DISPLAY_V_START, v->display_start_y); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_DISPLAY_V_END, v->display_end_y); + temp = v->active_start_x | (v->active_end_x << 16); + if (v->active_h_enable) + temp |= BIT(31); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_ACTIVE_HCTL, temp); + + temp = v->active_start_y; + if (v->active_v_enable) + temp |= BIT(31); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_ACTIVE_V_START, temp); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_ACTIVE_V_END, v->active_end_y); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_HSYNC_SKEW, v->hsync_skew); + temp = 0; + if (!v->hsync_polarity) + temp |= BIT(0); + if (!v->vsync_polarity) + temp |= BIT(1); + if (!v->de_polarity) + temp |= BIT(2); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_CTL_POLARITY, temp); + + return 0; +} + +int dsi_video_start(struct mdp3_intf *intf) +{ + pr_debug("dsi_video_start\n"); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_EN, BIT(0)); + wmb(); + intf->active = true; + return 0; +} + +int dsi_video_stop(struct mdp3_intf *intf) +{ + pr_debug("dsi_video_stop\n"); + MDP3_REG_WRITE(MDP3_REG_DSI_VIDEO_EN, 0); + wmb(); + intf->active = false; + return 0; +} + +int dsi_cmd_config(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg) +{ + u32 id_map = 0; + u32 trigger_en = 0; + + if (cfg->dsi_cmd.primary_dsi_cmd_id) + id_map = BIT(0); + if (cfg->dsi_cmd.secondary_dsi_cmd_id) + id_map = BIT(4); + + if (cfg->dsi_cmd.dsi_cmd_tg_intf_sel) + trigger_en = BIT(4); + + MDP3_REG_WRITE(MDP3_REG_DSI_CMD_MODE_ID_MAP, id_map); + MDP3_REG_WRITE(MDP3_REG_DSI_CMD_MODE_TRIGGER_EN, trigger_en); + + return 0; +} + +int dsi_cmd_start(struct mdp3_intf *intf) +{ + intf->active = true; + return 0; +} + +int dsi_cmd_stop(struct mdp3_intf *intf) +{ + intf->active = false; + return 0; +} + +int mdp3_intf_init(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg) +{ + int ret = 0; + switch (cfg->type) { + case MDP3_DMA_OUTPUT_SEL_LCDC: + intf->config = lcdc_config; + intf->start = lcdc_start; + intf->stop = lcdc_stop; + break; + case MDP3_DMA_OUTPUT_SEL_DSI_VIDEO: + intf->config = dsi_video_config; + intf->start = dsi_video_start; + intf->stop = dsi_video_stop; + break; + case MDP3_DMA_OUTPUT_SEL_DSI_CMD: + intf->config = dsi_cmd_config; + intf->start = dsi_cmd_start; + intf->stop = dsi_cmd_stop; + break; + + default: + return -EINVAL; + } + + intf->active = false; + if (intf->config) + ret = intf->config(intf, cfg); + + if (ret) { + pr_err("MDP interface initialization failed\n"); + return ret; + } + + intf->cfg = *cfg; + return 0; +} diff --git a/drivers/video/fbdev/msm/mdp3_dma.h b/drivers/video/fbdev/msm/mdp3_dma.h new file mode 100644 index 000000000000..2fb8427f0f9a --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3_dma.h @@ -0,0 +1,336 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDP3_DMA_H +#define MDP3_DMA_H + +#include <linux/sched.h> + +enum { + MDP3_DMA_P, + MDP3_DMA_S, + MDP3_DMA_E, + MDP3_DMA_MAX +}; + +enum { + MDP3_DMA_CAP_CURSOR = 0x1, + MDP3_DMA_CAP_COLOR_CORRECTION = 0x2, + MDP3_DMA_CAP_HISTOGRAM = 0x4, + MDP3_DMA_CAP_GAMMA_CORRECTION = 0x8, + MDP3_DMA_CAP_DITHER = 0x10, + MDP3_DMA_CAP_ALL = 0x1F +}; + +enum { + MDP3_DMA_OUTPUT_SEL_AHB, + MDP3_DMA_OUTPUT_SEL_DSI_CMD, + MDP3_DMA_OUTPUT_SEL_LCDC, + MDP3_DMA_OUTPUT_SEL_DSI_VIDEO, + MDP3_DMA_OUTPUT_SEL_MAX +}; + +enum { + MDP3_DMA_IBUF_FORMAT_RGB888, + MDP3_DMA_IBUF_FORMAT_RGB565, + MDP3_DMA_IBUF_FORMAT_XRGB8888, + MDP3_DMA_IBUF_FORMAT_UNDEFINED +}; + +enum { + MDP3_DMA_OUTPUT_PACK_PATTERN_RGB = 0x21, + MDP3_DMA_OUTPUT_PACK_PATTERN_RBG = 0x24, + MDP3_DMA_OUTPUT_PACK_PATTERN_BGR = 0x12, + MDP3_DMA_OUTPUT_PACK_PATTERN_BRG = 0x18, + MDP3_DMA_OUTPUT_PACK_PATTERN_GBR = 0x06, + MDP3_DMA_OUTPUT_PACK_PATTERN_GRB = 0x09, +}; + +enum { + MDP3_DMA_OUTPUT_PACK_ALIGN_LSB, + MDP3_DMA_OUTPUT_PACK_ALIGN_MSB +}; + +enum { + MDP3_DMA_OUTPUT_COMP_BITS_4, /*4 bits per color component*/ + MDP3_DMA_OUTPUT_COMP_BITS_5, + MDP3_DMA_OUTPUT_COMP_BITS_6, + MDP3_DMA_OUTPUT_COMP_BITS_8, +}; + +enum { + MDP3_DMA_CURSOR_FORMAT_ARGB888, +}; + +enum { + MDP3_DMA_COLOR_CORRECT_SET_1, + MDP3_DMA_COLOR_CORRECT_SET_2 +}; + +enum { + MDP3_DMA_LUT_POSITION_PRE, + MDP3_DMA_LUT_POSITION_POST +}; + +enum { + MDP3_DMA_LUT_DISABLE = 0x0, + MDP3_DMA_LUT_ENABLE_C0 = 0x01, + MDP3_DMA_LUT_ENABLE_C1 = 0x02, + MDP3_DMA_LUT_ENABLE_C2 = 0x04, + MDP3_DMA_LUT_ENABLE_ALL = 0x07, +}; + +enum { + MDP3_DMA_HISTOGRAM_BIT_MASK_NONE = 0X0, + MDP3_DMA_HISTOGRAM_BIT_MASK_ONE_MSB = 0x1, + MDP3_DMA_HISTOGRAM_BIT_MASK_TWO_MSB = 0x2, + MDP3_DMA_HISTOGRAM_BIT_MASK_THREE_MSB = 0x3 +}; + +enum { + MDP3_DMA_COLOR_FLIP_NONE, + MDP3_DMA_COLOR_FLIP_COMP1 = 0x1, + MDP3_DMA_COLOR_FLIP_COMP2 = 0x2, + MDP3_DMA_COLOR_FLIP_COMP3 = 0x4, +}; + +enum { + MDP3_DMA_CURSOR_BLEND_NONE = 0x0, + MDP3_DMA_CURSOR_BLEND_PER_PIXEL_ALPHA = 0x3, + MDP3_DMA_CURSOR_BLEND_CONSTANT_ALPHA = 0x5, + MDP3_DMA_CURSOR_BLEND_COLOR_KEYING = 0x9 +}; + +enum { + MDP3_DMA_HISTO_OP_START, + MDP3_DMA_HISTO_OP_STOP, + MDP3_DMA_HISTO_OP_CANCEL, + MDP3_DMA_HISTO_OP_RESET +}; + +enum { + MDP3_DMA_CALLBACK_TYPE_VSYNC = 0x01, + MDP3_DMA_CALLBACK_TYPE_DMA_DONE = 0x02, +}; + +struct mdp3_dma_source { + u32 format; + int width; + int height; + int x; + int y; + void *buf; + int stride; +}; + +struct mdp3_dma_output_config { + int dither_en; + u32 out_sel; + u32 bit_mask_polarity; + u32 color_components_flip; + u32 pack_pattern; + u32 pack_align; + u32 color_comp_out_bits; +}; + +struct mdp3_dma_cursor_blend_config { + u32 mode; + u32 transparent_color; /*color keying*/ + u32 transparency_mask; + u32 constant_alpha; +}; + +struct mdp3_dma_cursor { + int enable; /* enable cursor or not*/ + u32 format; + int width; + int height; + int x; + int y; + void *buf; + struct mdp3_dma_cursor_blend_config blend_config; +}; + +struct mdp3_dma_ccs { + u32 *mv1; /*set1 matrix vector, 3x3 */ + u32 *mv2; + u32 *pre_bv1; /*pre-bias vector for set1, 1x3*/ + u32 *pre_bv2; + u32 *post_bv1; /*post-bias vecotr for set1, */ + u32 *post_bv2; + u32 *pre_lv1; /*pre-limit vector for set 1, 1x6*/ + u32 *pre_lv2; + u32 *post_lv1; + u32 *post_lv2; +}; + +struct mdp3_dma_lut { + uint8_t *color0_lut1; + uint8_t *color1_lut1; + uint8_t *color2_lut1; + uint8_t *color0_lut2; + uint8_t *color1_lut2; + uint8_t *color2_lut2; +}; + +struct mdp3_dma_color_correct_config { + int ccs_enable; + int lut_enable; + u32 lut_sel; + u32 post_limit_sel; + u32 pre_limit_sel; + u32 post_bias_sel; + u32 pre_bias_sel; + u32 ccs_sel; + u32 lut_position; +}; + +struct mdp3_dma_histogram_config { + int frame_count; + u32 bit_mask_polarity; + u32 bit_mask; + int auto_clear_en; +}; + +struct mdp3_dma_histogram_data { + uint8_t r_max_value; + uint8_t r_min_value; + uint8_t b_max_value; + uint8_t b_min_value; + uint8_t g_max_value; + uint8_t g_min_value; + uint8_t r_data[32]; + uint8_t g_data[32]; + uint8_t b_data[32]; +}; + +struct mdp3_vsync_notification { + void (*handler)(void *arg); + void *arg; +}; + +struct mdp3_intf; + +struct mdp3_dma { + u32 dma_sel; + u32 capability; + int in_use; + int available; + int busy; + + spinlock_t dma_lock; + struct completion vsync_comp; + struct completion dma_comp; + ktime_t vsync_time; + struct mdp3_vsync_notification vsync_client; + u32 cb_type; + + struct mdp3_dma_output_config output_config; + struct mdp3_dma_source source_config; + + struct mdp3_dma_cursor cursor; + struct mdp3_dma_color_correct_config ccs_config; + struct mdp3_dma_histogram_config histogram_config; + + int (*start)(struct mdp3_dma *dma, struct mdp3_intf *intf); + + int (*stop)(struct mdp3_dma *dma, struct mdp3_intf *intf); + + int (*config_cursor)(struct mdp3_dma *dma, + struct mdp3_dma_cursor *cursor); + + int (*config_ccs)(struct mdp3_dma *dma, + struct mdp3_dma_color_correct_config *config, + struct mdp3_dma_ccs *ccs, + struct mdp3_dma_lut *lut); + + int (*update)(struct mdp3_dma *dma, void *buf); + + int (*update_cursor)(struct mdp3_dma *dma, int x, int y); + + int (*get_histo)(struct mdp3_dma *dma, + struct mdp3_dma_histogram_data *data); + + int (*config_histo)(struct mdp3_dma *dma, + struct mdp3_dma_histogram_config *histo_config); + + int (*histo_op)(struct mdp3_dma *dma, + u32 op); + + int (*histo_intr_status)(struct mdp3_dma *dma, int *status); + + int (*histo_intr_enable)(struct mdp3_dma *dma, u32 mask); + + int (*histo_intr_clear)(struct mdp3_dma *dma, u32 mask); + + void (*vsync_enable)(struct mdp3_dma *dma, + struct mdp3_vsync_notification *vsync_client); + + ktime_t (*get_vsync_time)(struct mdp3_dma *dma); + +}; + +struct mdp3_video_intf_cfg { + int hsync_period; + int hsync_pulse_width; + int vsync_period; + int vsync_pulse_width; + int display_start_x; + int display_end_x; + int display_start_y; + int display_end_y; + int active_start_x; + int active_end_x; + int active_h_enable; + int active_start_y; + int active_end_y; + int active_v_enable; + int hsync_skew; + int hsync_polarity; + int vsync_polarity; + int de_polarity; +}; + +struct mdp3_dsi_cmd_intf_cfg { + int primary_dsi_cmd_id; + int secondary_dsi_cmd_id; + int dsi_cmd_tg_intf_sel; +}; + +struct mdp3_intf_cfg { + u32 type; + struct mdp3_video_intf_cfg video; + struct mdp3_dsi_cmd_intf_cfg dsi_cmd; +}; + +struct mdp3_intf { + struct mdp3_intf_cfg cfg; + int active; + int available; + int in_use; + int (*config)(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg); + int (*start)(struct mdp3_intf *intf); + int (*stop)(struct mdp3_intf *intf); +}; + +int mdp3_dma_init(struct mdp3_dma *dma, + struct mdp3_dma_source *source_config, + struct mdp3_dma_output_config *output_config); + +int mdp3_intf_init(struct mdp3_intf *intf, struct mdp3_intf_cfg *cfg); + +void mdp3_dma_callback_enable(struct mdp3_dma *dma, int type); + +void mdp3_dma_callback_disable(struct mdp3_dma *dma, int type); + +#endif /* MDP3_DMA_H */ diff --git a/drivers/video/fbdev/msm/mdp3_hwio.h b/drivers/video/fbdev/msm/mdp3_hwio.h new file mode 100644 index 000000000000..2763f46ee4b1 --- /dev/null +++ b/drivers/video/fbdev/msm/mdp3_hwio.h @@ -0,0 +1,216 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDP3_HWIO_H +#define MDP3_HWIO_H + +#include <linux/bitops.h> + +/*synchronization*/ +#define MDP3_REG_SYNC_CONFIG_0 0x0300 +#define MDP3_REG_SYNC_CONFIG_1 0x0304 +#define MDP3_REG_SYNC_CONFIG_2 0x0308 +#define MDP3_REG_SYNC_STATUS_0 0x030c +#define MDP3_REG_SYNC_STATUS_1 0x0310 +#define MDP3_REG_SYNC_STATUS_2 0x0314 +#define MDP3_REG_PRIMARY_VSYNC_OUT_CTRL 0x0318 +#define MDP3_REG_SECONDARY_VSYNC_OUT_CTRL 0x031c +#define MDP3_REG_EXTERNAL_VSYNC_OUT_CTRL 0x0320 +#define MDP3_REG_VSYNC_SEL 0x0324 + +/*interrupt*/ +#define MDP3_REG_INTR_ENABLE 0x0020 +#define MDP3_REG_INTR_STATUS 0x0024 +#define MDP3_REG_INTR_CLEAR 0x0028 + +#define MDP3_REG_PRIMARY_RD_PTR_IRQ 0x021C +#define MDP3_REG_SECONDARY_RD_PTR_IRQ 0x0220 + +/*operation control*/ +#define MDP3_REG_DMA_P_START 0x0044 +#define MDP3_REG_DMA_S_START 0x0048 +#define MDP3_REG_DMA_E_START 0x004c + +#define MDP3_REG_DISPLAY_STATUS 0x0038 + +#define MDP3_REG_HW_VERSION 0x0070 +#define MDP3_REG_SW_RESET 0x0074 + +/*EBI*/ +#define MDP3_REG_EBI2_LCD0 0x003c +#define MDP3_REG_EBI2_LCD0_YSTRIDE 0x0050 + +/*DMA_P*/ +#define MDP3_REG_DMA_P_CONFIG 0x90000 +#define MDP3_REG_DMA_P_SIZE 0x90004 +#define MDP3_REG_DMA_P_IBUF_ADDR 0x90008 +#define MDP3_REG_DMA_P_IBUF_Y_STRIDE 0x9000C +#define MDP3_REG_DMA_P_PROFILE_EN 0x90020 +#define MDP3_REG_DMA_P_OUT_XY 0x90010 +#define MDP3_REG_DMA_P_CURSOR_FORMAT 0x90040 +#define MDP3_REG_DMA_P_CURSOR_SIZE 0x90044 +#define MDP3_REG_DMA_P_CURSOR_BUF_ADDR 0x90048 +#define MDP3_REG_DMA_P_CURSOR_POS 0x9004c +#define MDP3_REG_DMA_P_CURSOR_BLEND_CONFIG 0x90060 +#define MDP3_REG_DMA_P_CURSOR_BLEND_PARAM 0x90064 +#define MDP3_REG_DMA_P_CURSOR_BLEND_TRANS_MASK 0x90068 +#define MDP3_REG_DMA_P_COLOR_CORRECT_CONFIG 0x90070 +#define MDP3_REG_DMA_P_CSC_BYPASS 0X93004 +#define MDP3_REG_DMA_P_CSC_MV1 0x93400 +#define MDP3_REG_DMA_P_CSC_MV2 0x93440 +#define MDP3_REG_DMA_P_CSC_PRE_BV1 0x93500 +#define MDP3_REG_DMA_P_CSC_PRE_BV2 0x93540 +#define MDP3_REG_DMA_P_CSC_POST_BV1 0x93580 +#define MDP3_REG_DMA_P_CSC_POST_BV2 0x935c0 +#define MDP3_REG_DMA_P_CSC_PRE_LV1 0x93600 +#define MDP3_REG_DMA_P_CSC_PRE_LV2 0x93640 +#define MDP3_REG_DMA_P_CSC_POST_LV1 0x93680 +#define MDP3_REG_DMA_P_CSC_POST_LV2 0x936c0 +#define MDP3_REG_DMA_P_CSC_LUT1 0x93800 +#define MDP3_REG_DMA_P_CSC_LUT2 0x93c00 +#define MDP3_REG_DMA_P_HIST_START 0x94000 +#define MDP3_REG_DMA_P_HIST_FRAME_CNT 0x94004 +#define MDP3_REG_DMA_P_HIST_BIT_MASK 0x94008 +#define MDP3_REG_DMA_P_HIST_RESET_SEQ_START 0x9400c +#define MDP3_REG_DMA_P_HIST_CONTROL 0x94010 +#define MDP3_REG_DMA_P_HIST_INTR_STATUS 0x94014 +#define MDP3_REG_DMA_P_HIST_INTR_CLEAR 0x94018 +#define MDP3_REG_DMA_P_HIST_INTR_ENABLE 0x9401c +#define MDP3_REG_DMA_P_HIST_STOP_REQ 0x94020 +#define MDP3_REG_DMA_P_HIST_CANCEL_REQ 0x94024 +#define MDP3_REG_DMA_P_HIST_EXTRA_INFO_0 0x94028 +#define MDP3_REG_DMA_P_HIST_EXTRA_INFO_1 0x9402c +#define MDP3_REG_DMA_P_HIST_R_DATA 0x94100 +#define MDP3_REG_DMA_P_HIST_G_DATA 0x94200 +#define MDP3_REG_DMA_P_HIST_B_DATA 0x94300 +#define MDP3_REG_DMA_P_FETCH_CFG 0x90074 +#define MDP3_REG_DMA_P_DCVS_CTRL 0x90080 +#define MDP3_REG_DMA_P_DCVS_STATUS 0x90084 + +/*DMA_S*/ +#define MDP3_REG_DMA_S_CONFIG 0x90000 +#define MDP3_REG_DMA_S_SIZE 0x90004 +#define MDP3_REG_DMA_S_IBUF_ADDR 0x90008 +#define MDP3_REG_DMA_S_IBUF_Y_STRIDE 0x9000C +#define MDP3_REG_DMA_S_OUT_XY 0x90010 + +/*interface*/ +#define MDP3_REG_LCDC_EN 0xE0000 +#define MDP3_REG_LCDC_HSYNC_CTL 0xE0004 +#define MDP3_REG_LCDC_VSYNC_PERIOD 0xE0008 +#define MDP3_REG_LCDC_VSYNC_PULSE_WIDTH 0xE000C +#define MDP3_REG_LCDC_DISPLAY_HCTL 0xE0010 +#define MDP3_REG_LCDC_DISPLAY_V_START 0xE0014 +#define MDP3_REG_LCDC_DISPLAY_V_END 0xE0018 +#define MDP3_REG_LCDC_ACTIVE_HCTL 0xE001C +#define MDP3_REG_LCDC_ACTIVE_V_START 0xE0020 +#define MDP3_REG_LCDC_ACTIVE_V_END 0xE0024 +#define MDP3_REG_LCDC_BORDER_COLOR 0xE0028 +#define MDP3_REG_LCDC_UNDERFLOW_CTL 0xE002C +#define MDP3_REG_LCDC_HSYNC_SKEW 0xE0030 +#define MDP3_REG_LCDC_TEST_CTL 0xE0034 +#define MDP3_REG_LCDC_CTL_POLARITY 0xE0038 +#define MDP3_REG_LCDC_TEST_COL_VAR1 0xE003C +#define MDP3_REG_LCDC_TEST_COL_VAR2 0xE0040 +#define MDP3_REG_LCDC_UFLOW_HIDING_CTL 0xE0044 +#define MDP3_REG_LCDC_LOST_PIXEL_CNT_VALUE 0xE0048 + +#define MDP3_REG_DSI_VIDEO_EN 0xF0000 +#define MDP3_REG_DSI_VIDEO_HSYNC_CTL 0xF0004 +#define MDP3_REG_DSI_VIDEO_VSYNC_PERIOD 0xF0008 +#define MDP3_REG_DSI_VIDEO_VSYNC_PULSE_WIDTH 0xF000C +#define MDP3_REG_DSI_VIDEO_DISPLAY_HCTL 0xF0010 +#define MDP3_REG_DSI_VIDEO_DISPLAY_V_START 0xF0014 +#define MDP3_REG_DSI_VIDEO_DISPLAY_V_END 0xF0018 +#define MDP3_REG_DSI_VIDEO_ACTIVE_HCTL 0xF001C +#define MDP3_REG_DSI_VIDEO_ACTIVE_V_START 0xF0020 +#define MDP3_REG_DSI_VIDEO_ACTIVE_V_END 0xF0024 +#define MDP3_REG_DSI_VIDEO_BORDER_COLOR 0xF0028 +#define MDP3_REG_DSI_VIDEO_UNDERFLOW_CTL 0xF002C +#define MDP3_REG_DSI_VIDEO_HSYNC_SKEW 0xF0030 +#define MDP3_REG_DSI_VIDEO_TEST_CTL 0xF0034 +#define MDP3_REG_DSI_VIDEO_CTL_POLARITY 0xF0038 +#define MDP3_REG_DSI_VIDEO_TEST_COL_VAR1 0xF003C +#define MDP3_REG_DSI_VIDEO_TEST_COL_VAR2 0xF0040 +#define MDP3_REG_DSI_VIDEO_UFLOW_HIDING_CTL 0xF0044 +#define MDP3_REG_DSI_VIDEO_LOST_PIXEL_CNT_VALUE 0xF0048 + +#define MDP3_REG_DSI_CMD_MODE_ID_MAP 0xF1000 +#define MDP3_REG_DSI_CMD_MODE_TRIGGER_EN 0xF1004 + +/*interrupt mask*/ + +#define MDP3_INTR_DP0_ROI_DONE_BIT BIT(0) +#define MDP3_INTR_DP1_ROI_DONE_BIT BIT(1) +#define MDP3_INTR_DMA_S_DONE_BIT BIT(2) +#define MDP3_INTR_DMA_E_DONE_BIT BIT(3) +#define MDP3_INTR_DP0_TERMINAL_FRAME_DONE_BIT BIT(4) +#define MDP3_INTR_DP1_TERMINAL_FRAME_DONE_BIT BIT(5) +#define MDP3_INTR_DMA_TV_DONE_BIT BIT(6) +#define MDP3_INTR_TV_ENCODER_UNDER_RUN_BIT BIT(7) +#define MDP3_INTR_SYNC_PRIMARY_LINE_BIT BIT(8) +#define MDP3_INTR_SYNC_SECONDARY_LINE_BIT BIT(9) +#define MDP3_INTR_SYNC_EXTERNAL_LINE_BIT BIT(10) +#define MDP3_INTR_DP0_FETCH_DONE_BIT BIT(11) +#define MDP3_INTR_DP1_FETCH_DONE_BIT BIT(12) +#define MDP3_INTR_TV_OUT_FRAME_START_BIT BIT(13) +#define MDP3_INTR_DMA_P_DONE_BIT BIT(14) +#define MDP3_INTR_LCDC_START_OF_FRAME_BIT BIT(15) +#define MDP3_INTR_LCDC_UNDERFLOW_BIT BIT(16) +#define MDP3_INTR_DMA_P_LINE_BIT BIT(17) +#define MDP3_INTR_DMA_S_LINE_BIT BIT(18) +#define MDP3_INTR_DMA_E_LINE_BIT BIT(19) +#define MDP3_INTR_DMA_P_HISTO_BIT BIT(20) +#define MDP3_INTR_DTV_OUT_DONE_BIT BIT(21) +#define MDP3_INTR_DTV_OUT_START_OF_FRAME_BIT BIT(22) +#define MDP3_INTR_DTV_OUT_UNDERFLOW_BIT BIT(23) +#define MDP3_INTR_DTV_OUT_LINE_BIT BIT(24) +#define MDP3_INTR_DMA_P_AUTO_FREFRESH_START_BIT BIT(25) +#define MDP3_INTR_DMA_S_AUTO_FREFRESH_START_BIT BIT(26) +#define MDP3_INTR_QPIC_EOF_ENABLE_BIT BIT(27) + +enum { + MDP3_INTR_DP0_ROI_DONE, + MDP3_INTR_DP1_ROI_DONE, + MDP3_INTR_DMA_S_DONE, + MDP3_INTR_DMA_E_DONE, + MDP3_INTR_DP0_TERMINAL_FRAME_DONE, + MDP3_INTR_DP1_TERMINAL_FRAME_DONE, + MDP3_INTR_DMA_TV_DONE, + MDP3_INTR_TV_ENCODER_UNDER_RUN, + MDP3_INTR_SYNC_PRIMARY_LINE, + MDP3_INTR_SYNC_SECONDARY_LINE, + MDP3_INTR_SYNC_EXTERNAL_LINE, + MDP3_INTR_DP0_FETCH_DONE, + MDP3_INTR_DP1_FETCH_DONE, + MDP3_INTR_TV_OUT_FRAME_START, + MDP3_INTR_DMA_P_DONE, + MDP3_INTR_LCDC_START_OF_FRAME, + MDP3_INTR_LCDC_UNDERFLOW, + MDP3_INTR_DMA_P_LINE, + MDP3_INTR_DMA_S_LINE, + MDP3_INTR_DMA_E_LINE, + MDP3_INTR_DMA_P_HISTO, + MDP3_INTR_DTV_OUT_DONE, + MDP3_INTR_DTV_OUT_START_OF_FRAME, + MDP3_INTR_DTV_OUT_UNDERFLOW, + MDP3_INTR_DTV_OUT_LINE, + MDP3_INTR_DMA_P_AUTO_FREFRESH_START, + MDP3_INTR_DMA_S_AUTO_FREFRESH_START, + MDP3_INTR_QPIC_EOF_ENABLE, +}; + +#define MDP3_DMA_P_HIST_INTR_RESET_DONE_BIT BIT(0) +#define MDP3_DMA_P_HIST_INTR_HIST_DONE_BIT BIT(1) + +#endif /* MDP3_HWIO_H */ |
