/* linux/drivers/media/video/samsung/csis.c * * Copyright (c) 2010 Samsung Electronics Co,. Ltd. * http://www.samsung.com/ * * MIPI-CSI2 Support file for FIMC driver * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csis.h" static struct s3c_csis_info *s3c_csis; static struct s3c_platform_csis *to_csis_plat(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); return (struct s3c_platform_csis *) pdev->dev.platform_data; } static int s3c_csis_set_info(void) { s3c_csis = (struct s3c_csis_info *) \ kmalloc(sizeof(struct s3c_csis_info), GFP_KERNEL); if (!s3c_csis) { err("no memory for configuration\n"); return -ENOMEM; } strcpy(s3c_csis->name, S3C_CSIS_NAME); s3c_csis->nr_lanes = S3C_CSIS_NR_LANES; return 0; } static void s3c_csis_reset(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg |= S3C_CSIS_CONTROL_RESET; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_set_nr_lanes(int lanes) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONFIG); cfg &= ~S3C_CSIS_CONFIG_NR_LANE_MASK; if (lanes == 1) cfg |= S3C_CSIS_CONFIG_NR_LANE_1; else cfg |= S3C_CSIS_CONFIG_NR_LANE_2; writel(cfg, s3c_csis->regs + S3C_CSIS_CONFIG); } static void s3c_csis_enable_interrupt(void) { u32 cfg = 0; /* enable all interrupts */ cfg |= S3C_CSIS_INTMSK_EVEN_BEFORE_ENABLE | \ S3C_CSIS_INTMSK_EVEN_AFTER_ENABLE | \ S3C_CSIS_INTMSK_ODD_BEFORE_ENABLE | \ S3C_CSIS_INTMSK_ODD_AFTER_ENABLE | \ S3C_CSIS_INTMSK_ERR_SOT_HS_ENABLE | \ S3C_CSIS_INTMSK_ERR_ESC_ENABLE | \ S3C_CSIS_INTMSK_ERR_CTRL_ENABLE | \ S3C_CSIS_INTMSK_ERR_ECC_ENABLE | \ S3C_CSIS_INTMSK_ERR_CRC_ENABLE | \ S3C_CSIS_INTMSK_ERR_ID_ENABLE; writel(cfg, s3c_csis->regs + S3C_CSIS_INTMSK); } static void s3c_csis_disable_interrupt(void) { /* disable all interrupts */ writel(0, s3c_csis->regs + S3C_CSIS_INTMSK); } static void s3c_csis_system_on(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg |= S3C_CSIS_CONTROL_ENABLE; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_system_off(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg &= ~S3C_CSIS_CONTROL_ENABLE; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_phy_on(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); cfg |= S3C_CSIS_DPHYCTRL_ENABLE; writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); } static void s3c_csis_phy_off(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); cfg &= ~S3C_CSIS_DPHYCTRL_ENABLE; writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); } #ifdef CONFIG_MIPI_CSI_ADV_FEATURE static void s3c_csis_update_shadow(void) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg |= S3C_CSIS_CONTROL_UPDATE_SHADOW; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_set_data_align(int align) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg &= ~S3C_CSIS_CONTROL_ALIGN_MASK; if (align == 24) cfg |= S3C_CSIS_CONTROL_ALIGN_24BIT; else cfg |= S3C_CSIS_CONTROL_ALIGN_32BIT; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_set_wclk(int extclk) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONTROL); cfg &= ~S3C_CSIS_CONTROL_WCLK_MASK; if (extclk) cfg |= S3C_CSIS_CONTROL_WCLK_EXTCLK; else cfg |= S3C_CSIS_CONTROL_WCLK_PCLK; writel(cfg, s3c_csis->regs + S3C_CSIS_CONTROL); } static void s3c_csis_set_format(enum mipi_format fmt) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_CONFIG); cfg &= ~S3C_CSIS_CONFIG_FORMAT_MASK; cfg |= (fmt << S3C_CSIS_CONFIG_FORMAT_SHIFT); writel(cfg, s3c_csis->regs + S3C_CSIS_CONFIG); } static void s3c_csis_set_resol(int width, int height) { u32 cfg = 0; cfg |= width << S3C_CSIS_RESOL_HOR_SHIFT; cfg |= height << S3C_CSIS_RESOL_VER_SHIFT; writel(cfg, s3c_csis->regs + S3C_CSIS_RESOL); } static void s3c_csis_set_hs_settle(int settle) { u32 cfg; cfg = readl(s3c_csis->regs + S3C_CSIS_DPHYCTRL); cfg &= ~S3C_CSIS_DPHYCTRL_HS_SETTLE_MASK; cfg |= (settle << S3C_CSIS_DPHYCTRL_HS_SETTLE_SHIFT); writel(cfg, s3c_csis->regs + S3C_CSIS_DPHYCTRL); } #endif void s3c_csis_start(int lanes, int settle, int align, int width, int height, int pixel_format) { struct platform_device *pdev = to_platform_device(s3c_csis->dev); struct s3c_platform_csis *pdata; pdata = to_csis_plat(s3c_csis->dev); if (pdata->cfg_phy_global) pdata->cfg_phy_global(pdev, 1); s3c_csis_reset(); s3c_csis_set_nr_lanes(lanes); #ifdef CONFIG_MIPI_CSI_ADV_FEATURE /* FIXME: how configure the followings with FIMC dynamically? */ s3c_csis_set_hs_settle(settle); /* s5k6aa */ s3c_csis_set_data_align(align); s3c_csis_set_wclk(0); if (pixel_format == V4L2_PIX_FMT_JPEG) s3c_csis_set_format(MIPI_USER_DEF_PACKET_1); else s3c_csis_set_format(MIPI_CSI_YCBCR422_8BIT); s3c_csis_set_resol(width, height); s3c_csis_update_shadow(); #endif s3c_csis_enable_interrupt(); s3c_csis_system_on(); s3c_csis_phy_on(); info("Samsung MIPI-CSI2 operation started\n"); } static void s3c_csis_stop(struct platform_device *pdev) { struct s3c_platform_csis *plat; s3c_csis_disable_interrupt(); s3c_csis_system_off(); s3c_csis_phy_off(); plat = to_csis_plat(&pdev->dev); if (plat->cfg_phy_global) plat->cfg_phy_global(pdev, 0); } static irqreturn_t s3c_csis_irq(int irq, void *dev_id) { u32 cfg; /* just clearing the pends */ cfg = readl(s3c_csis->regs + S3C_CSIS_INTSRC); writel(cfg, s3c_csis->regs + S3C_CSIS_INTSRC); return IRQ_HANDLED; } static int s3c_csis_clk_on(struct platform_device *pdev) { struct s3c_platform_csis *pdata; struct clk *parent, *mout_csis; pdata = to_csis_plat(&pdev->dev); /* mout_mpll */ parent = clk_get(&pdev->dev, pdata->srclk_name); if (IS_ERR(parent)) { err("failed to get parent clock for csis\n"); return -EINVAL; } /* mout_csis */ mout_csis = clk_get(&pdev->dev, "mout_csis"); /* sclk_csis */ s3c_csis->clock = clk_get(&pdev->dev, pdata->clk_name); if (IS_ERR(s3c_csis->clock)) { err("failed to get csis clock source\n"); return -EINVAL; } clk_set_parent(mout_csis, parent); clk_set_parent(s3c_csis->clock, mout_csis); /* Turn on csis power domain regulator */ regulator_enable(s3c_csis->regulator); /* clock enable for csis */ clk_enable(s3c_csis->clock); return 0; } static int s3c_csis_clk_off(struct platform_device *pdev) { struct s3c_platform_csis *plat; plat = to_csis_plat(&pdev->dev); /* sclk_csis */ s3c_csis->clock = clk_get(&pdev->dev, plat->clk_name); if (IS_ERR(s3c_csis->clock)) { err("failed to get csis clock source\n"); return -EINVAL; } /* clock disable for csis */ clk_disable(s3c_csis->clock); /* Turn off csis power domain regulator */ regulator_disable(s3c_csis->regulator); return 0; } static int s3c_csis_probe(struct platform_device *pdev) { struct s3c_platform_csis *pdata; struct resource *res; s3c_csis_set_info(); s3c_csis->dev = &pdev->dev; pdata = to_csis_plat(&pdev->dev); if (pdata->cfg_gpio) pdata->cfg_gpio(); /* Get csis power domain regulator */ s3c_csis->regulator = regulator_get(&pdev->dev, "pd"); if (IS_ERR(s3c_csis->regulator)) { err("%s: failed to get resource %s\n", __func__, "s3c-csis"); return PTR_ERR(s3c_csis->regulator); } /* clock & power on */ s3c_csis_clk_on(pdev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { err("failed to get io memory region\n"); return -EINVAL; } res = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (!res) { err("failed to request io memory region\n"); return -EINVAL; } /* ioremap for register block */ s3c_csis->regs = ioremap(res->start, res->end - res->start + 1); if (!s3c_csis->regs) { err("failed to remap io region\n"); return -EINVAL; } /* irq */ s3c_csis->irq = platform_get_irq(pdev, 0); if (request_irq(s3c_csis->irq, s3c_csis_irq, IRQF_DISABLED, \ s3c_csis->name, s3c_csis)) err("request_irq failed\n"); info("Samsung MIPI-CSI2 driver probed successfully\n"); return 0; } static int s3c_csis_remove(struct platform_device *pdev) { s3c_csis_stop(pdev); kfree(s3c_csis); return 0; } /* sleep */ int s3c_csis_suspend(struct platform_device *pdev, pm_message_t state) { s3c_csis_clk_off(pdev); return 0; } /* wakeup */ int s3c_csis_resume(struct platform_device *pdev) { s3c_csis_clk_on(pdev); return 0; } static struct platform_driver s3c_csis_driver = { .probe = s3c_csis_probe, .remove = s3c_csis_remove, .suspend = s3c_csis_suspend, .resume = s3c_csis_resume, .driver = { .name = "s5p-mipi-csis", .owner = THIS_MODULE, }, }; static int s3c_csis_register(void) { platform_driver_register(&s3c_csis_driver); return 0; } static void s3c_csis_unregister(void) { platform_driver_unregister(&s3c_csis_driver); } module_init(s3c_csis_register); module_exit(s3c_csis_unregister); MODULE_AUTHOR("Jinsung, Yang "); MODULE_AUTHOR("Sewoon, Park "); MODULE_DESCRIPTION("MIPI-CSI2 support for FIMC driver"); MODULE_LICENSE("GPL");