aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c')
-rw-r--r--drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c b/drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c
new file mode 100644
index 0000000..3e128b8
--- /dev/null
+++ b/drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c
@@ -0,0 +1,502 @@
+/* /linux/driver/video/samsung/s5p_fimd_lite.c
+ *
+ * Samsung SoC FIMD Lite driver.
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+
+#include <plat/clock.h>
+#include <plat/fb.h>
+#include <plat/cpu.h>
+#include <plat/fimd_lite_ext.h>
+#include <plat/regs-fb.h>
+
+#include <mach/map.h>
+
+#include "s5p_fimd_ext.h"
+#include "s5p_fimd_lite.h"
+#include "regs_fimd_lite.h"
+
+static void *to_fimd_lite_platform_data(struct s5p_fimd_ext_device *fx_dev)
+{
+ return fx_dev->dev.platform_data ? (void *)fx_dev->dev.platform_data :
+ NULL;
+}
+
+static void s5p_fimd_lite_set_par(struct s5p_fimd_lite *fimd_lite,
+ unsigned int win_id)
+{
+ struct exynos_drm_fimd_pdata *lcd;
+ struct fb_videomode timing;
+ unsigned int cfg;
+
+ lcd = fimd_lite->lcd;
+ timing = lcd->panel.timing;
+
+ /* set window control */
+ cfg = readl(fimd_lite->iomem_base + S5P_WINCON(win_id));
+
+ cfg &= ~(S5P_WINCON_BITSWP_ENABLE | S5P_WINCON_BYTESWP_ENABLE | \
+ S5P_WINCON_HAWSWP_ENABLE | S5P_WINCON_WSWP_ENABLE | \
+ S5P_WINCON_BURSTLEN_MASK | S5P_WINCON_BPPMODE_MASK | \
+ S5P_WINCON_INRGB_MASK | S5P_WINCON_DATAPATH_MASK);
+
+ /* DATAPATH is LOCAL */
+ cfg |= S5P_WINCON_DATAPATH_LOCAL;
+
+ /* pixel format is unpacked RGB888 */
+ cfg |= S5P_WINCON_INRGB_RGB | S5P_WINCON_BPPMODE_32BPP;
+
+ writel(cfg, fimd_lite->iomem_base + S5P_WINCON(win_id));
+
+ /* set window position to x=0, y=0*/
+ cfg = S5P_VIDOSD_LEFT_X(0) | S5P_VIDOSD_TOP_Y(0);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDOSD_A(win_id));
+
+ cfg = S5P_VIDOSD_RIGHT_X(timing.xres - 1) |
+ S5P_VIDOSD_BOTTOM_Y(timing.yres - 1);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDOSD_B(win_id));
+
+ /* set window size for window0*/
+ cfg = S5P_VIDOSD_SIZE(timing.xres * timing.yres);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDOSD_C(win_id));
+
+ return;
+}
+
+static void s5p_fimd_lite_set_clock(struct s5p_fimd_lite *fimd_lite)
+{
+ unsigned int cfg = 0, div = 0;
+ unsigned int pixel_clock, src_clock, max_clock;
+ struct clk *clk;
+ struct exynos_drm_fimd_pdata *lcd;
+ struct fb_videomode timing;
+
+ lcd = fimd_lite->lcd;
+ timing = lcd->panel.timing;
+
+ clk = fimd_lite->clk;
+
+ max_clock = 86 * 1000000;
+
+ pixel_clock = timing.refresh *
+ (timing.left_margin + timing.right_margin +
+ timing.hsync_len + timing.xres) * (timing.upper_margin +
+ timing.lower_margin + timing.vsync_len + timing.yres);
+
+ src_clock = clk_get_rate(clk->parent);
+
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDCON0);
+ cfg &= ~(S5P_VIDCON0_VCLKEN_MASK | S5P_VIDCON0_CLKVALUP_MASK |
+ S5P_VIDCON0_CLKVAL_F(0xFF));
+ cfg |= (S5P_VIDCON0_CLKVALUP_ALWAYS | S5P_VIDCON0_VCLKEN_NORMAL);
+
+ cfg |= S5P_VIDCON0_CLKSEL_HCLK;
+
+ if (pixel_clock > max_clock)
+ pixel_clock = max_clock;
+
+ div = (unsigned int)(src_clock / pixel_clock);
+ if (src_clock % pixel_clock)
+ div++;
+
+ cfg |= S5P_VIDCON0_CLKVAL_F(div - 1);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDCON0);
+
+ return;
+}
+
+static void s5p_fimd_lite_window_on(struct s5p_fimd_lite *fimd_lite,
+ unsigned int win_id, unsigned int enable)
+{
+ unsigned int cfg;
+
+ /* enable window */
+ cfg = readl(fimd_lite->iomem_base + S5P_WINCON(win_id));
+
+ cfg &= ~S5P_WINCON_ENWIN_ENABLE;
+
+ if (enable)
+ cfg |= S5P_WINCON_ENWIN_ENABLE;
+
+ writel(cfg, fimd_lite->iomem_base + S5P_WINCON(win_id));
+}
+
+static void s5p_fimd_lite_lcd_on(struct s5p_fimd_lite *fimd_lite,
+ unsigned int enable)
+{
+ unsigned int cfg;
+
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDCON0);
+
+ cfg &= ~(S5P_VIDCON0_ENVID_ENABLE | S5P_VIDCON0_ENVID_F_ENABLE);
+
+ if (enable)
+ cfg |= (S5P_VIDCON0_ENVID_ENABLE | S5P_VIDCON0_ENVID_F_ENABLE);
+
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDCON0);
+}
+
+void s5p_fimd_lite_get_vsync_interrupt(struct s5p_fimd_lite *fimd_lite,
+ unsigned int enable)
+{
+ unsigned int cfg;
+
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDINTCON0);
+ cfg &= ~(S5P_VIDINTCON0_INTFRMEN_ENABLE | S5P_VIDINTCON0_INT_ENABLE |
+ S5P_VIDINTCON0_FRAMESEL0_VSYNC);
+
+ if (enable) {
+ cfg |= (S5P_VIDINTCON0_INTFRMEN_ENABLE |
+ S5P_VIDINTCON0_INT_ENABLE |
+ S5P_VIDINTCON0_FRAMESEL0_VSYNC);
+ } else {
+ cfg |= (S5P_VIDINTCON0_INTFRMEN_DISABLE |
+ S5P_VIDINTCON0_INT_DISABLE);
+
+ cfg &= ~S5P_VIDINTCON0_FRAMESEL0_VSYNC;
+ }
+
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDINTCON0);
+}
+
+static void s5p_change_dynamic_refresh(struct s5p_fimd_dynamic_refresh
+ *fimd_refresh, struct s5p_fimd_ext_device *fx_dev)
+{
+ unsigned int cfg = 0, ret = 0;
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+ struct exynos_drm_fimd_pdata *lcd;
+ struct fb_videomode timing;
+ unsigned long flags;
+ u32 vclk, src_clk, refresh;
+
+ lcd = fimd_lite->lcd;
+ timing = lcd->panel.timing;
+
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDCON0);
+ cfg &= ~(S5P_VIDCON0_CLKVALUP_START_FRAME | S5P_VIDCON0_CLKVAL_F(0xFF));
+ cfg |= (S5P_VIDCON0_CLKVALUP_ALWAYS | S5P_VIDCON0_VCLKEN_NORMAL);
+ cfg |= S5P_VIDCON0_CLKVAL_F(fimd_refresh->clkdiv - 1);
+
+ if (!irqs_disabled())
+ local_irq_save(flags);
+
+ if (timing.refresh == 60) {
+ while (1) {
+ ret = (__raw_readl(fimd_lite->iomem_base +
+ S5P_VIDCON1) >> 13) &
+ S5P_VIDCON1_VSTATUS_MASK;
+ if (ret == S5P_VIDCON1_VSTATUS_BACKPORCH) {
+ __raw_writel(cfg, fimd_lite->iomem_base +
+ S5P_VIDCON0);
+ ret = (__raw_readl(fimd_refresh->regs +
+ VIDCON1) >> 13) &
+ S5P_VIDCON1_VSTATUS_MASK;
+ if (ret == S5P_VIDCON1_VSTATUS_ACTIVE) {
+ __raw_writel(cfg,
+ fimd_refresh->regs + VIDCON0);
+ break;
+ }
+ }
+ }
+ } else {
+ while (1) {
+ ret = (__raw_readl(fimd_refresh->regs + VIDCON1) >> 13)
+ & S5P_VIDCON1_VSTATUS_MASK;
+ if (ret == S5P_VIDCON1_VSTATUS_ACTIVE) {
+ ret = (__raw_readl(fimd_lite->iomem_base +
+ S5P_VIDCON1) >> 13) &
+ S5P_VIDCON1_VSTATUS_MASK;
+ if (ret == S5P_VIDCON1_VSTATUS_FRONTPORCH) {
+ __raw_writel(cfg,
+ fimd_refresh->regs + VIDCON0);
+ __raw_writel(cfg,
+ fimd_lite->iomem_base +
+ S5P_VIDCON0);
+ break;
+ }
+ }
+ }
+ }
+ if (irqs_disabled())
+ local_irq_restore(flags);
+
+ src_clk = clk_get_rate(fimd_lite->clk->parent);
+ vclk = timing.refresh * (timing.left_margin + timing.hsync_len +
+ timing.right_margin + timing.xres) *
+ (timing.upper_margin + timing.vsync_len +
+ timing.lower_margin + timing.yres);
+
+ refresh = timing.refresh -
+ ((vclk - (src_clk / fimd_refresh->clkdiv)) / MHZ);
+ dev_dbg(fimd_lite->dev, "expected refresh rate: %d fps\n", refresh);
+}
+
+static irqreturn_t s5p_fimd_lite_irq_frame(int irq, void *dev_id)
+{
+ struct s5p_fimd_lite *fimd_lite;
+
+ fimd_lite = (struct s5p_fimd_lite *)dev_id;
+
+ disable_irq_nosync(fimd_lite->irq);
+
+ enable_irq(fimd_lite->irq);
+
+ return IRQ_HANDLED;
+}
+
+static void s5p_fimd_lite_logic_start(struct s5p_fimd_lite *fimd_lite,
+ unsigned int enable)
+{
+ unsigned int cfg;
+
+ cfg = 0x2ff47;
+
+ if (enable)
+ writel(cfg, fimd_lite->iomem_base + S5P_GPOUTCON0);
+ else
+ writel(0, fimd_lite->iomem_base + S5P_GPOUTCON0);
+}
+
+static void s5p_fimd_lite_lcd_init(struct s5p_fimd_lite *fimd_lite)
+{
+ unsigned int cfg, rgb_mode, win_id = 0;
+ struct exynos_drm_fimd_pdata *lcd;
+ struct fb_videomode timing;
+
+ lcd = fimd_lite->lcd;
+ timing = lcd->panel.timing;
+
+ cfg = 0;
+ cfg |= lcd->vidcon1;
+
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDCON1);
+
+ /* set timing */
+ cfg = 0;
+ cfg |= S5P_VIDTCON0_VBPD(timing.upper_margin - 1);
+ cfg |= S5P_VIDTCON0_VFPD(timing.lower_margin - 1);
+ cfg |= S5P_VIDTCON0_VSPW(timing.vsync_len - 1);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDTCON0);
+
+ cfg = 0;
+ cfg |= S5P_VIDTCON1_HBPD(timing.left_margin - 1);
+ cfg |= S5P_VIDTCON1_HFPD(timing.right_margin - 1);
+ cfg |= S5P_VIDTCON1_HSPW(timing.hsync_len - 1);
+
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDTCON1);
+
+ /* set lcd size */
+ cfg = 0;
+ cfg |= S5P_VIDTCON2_HOZVAL(timing.xres - 1);
+ cfg |= S5P_VIDTCON2_LINEVAL(timing.yres - 1);
+
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDTCON2);
+
+ writel(0, fimd_lite->iomem_base + S5P_DITHMODE);
+
+ /* set output to RGB */
+ rgb_mode = 0; /* MODE_RGB_P */
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDCON0);
+ cfg &= ~S5P_VIDCON0_VIDOUT_MASK;
+
+ cfg |= S5P_VIDCON0_VIDOUT_RGB;
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDCON0);
+
+ /* set display mode */
+ cfg = readl(fimd_lite->iomem_base + S5P_VIDCON0);
+ cfg &= ~S5P_VIDCON0_PNRMODE_MASK;
+ cfg |= (rgb_mode << S5P_VIDCON0_PNRMODE_SHIFT);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDCON0);
+
+ s5p_fimd_lite_get_vsync_interrupt(fimd_lite, 0);
+
+ /* set par */
+ s5p_fimd_lite_set_par(fimd_lite, win_id);
+
+ /* set buffer size */
+ cfg = S5P_VIDADDR_PAGEWIDTH(timing.xres * lcd->bpp / 8);
+ writel(cfg, fimd_lite->iomem_base + S5P_VIDADDR_SIZE(win_id));
+
+ /* set clock */
+ s5p_fimd_lite_set_clock(fimd_lite);
+
+ return;
+}
+
+static int s5p_fimd_lite_setup(struct s5p_fimd_ext_device *fx_dev,
+ unsigned int enable)
+{
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+
+ s5p_fimd_lite_logic_start(fimd_lite, enable);
+
+ s5p_fimd_lite_lcd_init(fimd_lite);
+
+
+ s5p_fimd_lite_window_on(fimd_lite, 0, 1);
+
+ return 0;
+}
+
+static int s5p_fimd_lite_start(struct s5p_fimd_ext_device *fx_dev)
+{
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+
+ s5p_fimd_lite_lcd_on(fimd_lite, 1);
+
+ return 0;
+}
+
+static void s5p_fimd_lite_stop(struct s5p_fimd_ext_device *fx_dev)
+{
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+
+ s5p_fimd_lite_lcd_on(fimd_lite, 0);
+}
+
+static void s5p_fimd_lite_power_on(struct s5p_fimd_ext_device *fx_dev)
+{
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+
+ clk_enable(fimd_lite->clk);
+}
+
+static void s5p_fimd_lite_power_off(struct s5p_fimd_ext_device *fx_dev)
+{
+ struct s5p_fimd_lite *fimd_lite = fimd_ext_get_drvdata(fx_dev);
+
+ clk_disable(fimd_lite->clk);
+}
+
+static int s5p_fimd_lite_probe(struct s5p_fimd_ext_device *fx_dev)
+{
+ struct clk *sclk = NULL;
+ struct resource *res;
+ struct s5p_fimd_lite *fimd_lite;
+ int ret = -1;
+
+ fimd_lite = kzalloc(sizeof(struct s5p_fimd_lite), GFP_KERNEL);
+ if (!fimd_lite) {
+ dev_err(&fx_dev->dev, "failed to alloc fimd_lite object.\n");
+ return -EFAULT;
+ }
+
+ fimd_lite->dev = &fx_dev->dev;
+ fimd_lite->lcd = (struct exynos_drm_fimd_pdata *)
+ to_fimd_lite_platform_data(fx_dev);
+
+ res = s5p_fimd_ext_get_resource(fx_dev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&fx_dev->dev, "failed to get io memory region.\n");
+ ret = -EINVAL;
+ goto err0;
+ }
+
+ fimd_lite->iomem_base = ioremap(res->start, resource_size(res));
+ if (!fimd_lite->iomem_base) {
+ dev_err(&fx_dev->dev, "failed to remap io region\n");
+ ret = -EFAULT;
+ goto err0;
+ }
+
+ fimd_lite->clk = clk_get(&fx_dev->dev, "mdnie0");
+ if (IS_ERR(fimd_lite->clk)) {
+ dev_err(&fx_dev->dev, "failed to get FIMD LITE clock source\n");
+ ret = -EINVAL;
+ goto err1;
+ }
+
+ sclk = clk_get(&fx_dev->dev, "sclk_mdnie");
+ if (IS_ERR(sclk)) {
+ dev_err(&fx_dev->dev, "failed to get sclk_mdnie clock\n");
+ ret = -EINVAL;
+ goto err2;
+ }
+ fimd_lite->clk->parent = sclk;
+
+ fimd_lite->irq = s5p_fimd_ext_get_irq(fx_dev, 0);
+
+ /* register interrupt handler for fimd-lite. */
+ if (request_irq(fimd_lite->irq, s5p_fimd_lite_irq_frame, IRQF_DISABLED,
+ fx_dev->name, (void *)fimd_lite)) {
+ dev_err(&fx_dev->dev, "request_irq failed\n");
+ ret = -EINVAL;
+ goto err3;
+ }
+
+ fimd_ext_set_drvdata(fx_dev, fimd_lite);
+
+ dev_info(&fx_dev->dev, "fimd lite driver has been probed.\n");
+
+ return 0;
+
+err3:
+ free_irq(fimd_lite->irq, fx_dev);
+err2:
+ clk_put(sclk);
+err1:
+ iounmap(fimd_lite->iomem_base);
+ clk_put(fimd_lite->clk);
+err0:
+ kfree(fimd_lite);
+
+ return ret;
+
+}
+
+static struct s5p_fimd_ext_driver fimd_ext_driver = {
+ .driver = {
+ .name = "fimd_lite",
+ .owner = THIS_MODULE,
+ },
+ .change_clock = s5p_change_dynamic_refresh,
+ .power_on = s5p_fimd_lite_power_on,
+ .power_off = s5p_fimd_lite_power_off,
+ .setup = s5p_fimd_lite_setup,
+ .start = s5p_fimd_lite_start,
+ .stop = s5p_fimd_lite_stop,
+ .probe = s5p_fimd_lite_probe,
+};
+
+static int __init s5p_fimd_lite_init(void)
+{
+ return s5p_fimd_ext_driver_register(&fimd_ext_driver);
+}
+
+static void __exit s5p_fimd_lite_exit(void)
+{
+}
+
+arch_initcall(s5p_fimd_lite_init);
+module_exit(s5p_fimd_lite_exit);
+
+MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>");
+MODULE_DESCRIPTION("FIMD Lite Driver");
+MODULE_LICENSE("GPL");
+