diff options
Diffstat (limited to 'drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c')
-rw-r--r-- | drivers/video/samsung_duallcd/extension/s5p_fimd_lite.c | 502 |
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"); + |