diff options
Diffstat (limited to 'drivers/media/video/samsung/fimc/csis.c')
-rw-r--r-- | drivers/media/video/samsung/fimc/csis.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/drivers/media/video/samsung/fimc/csis.c b/drivers/media/video/samsung/fimc/csis.c new file mode 100644 index 0000000..f512d10 --- /dev/null +++ b/drivers/media/video/samsung/fimc/csis.c @@ -0,0 +1,431 @@ +/* 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <linux/slab.h> + +#include <linux/io.h> +#include <linux/memory.h> +#include <plat/clock.h> +#include <plat/regs-csis.h> +#include <plat/csis.h> +#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 <jsgood.yang@samsung.com>"); +MODULE_AUTHOR("Sewoon, Park <seuni.park@samsung.com>"); +MODULE_DESCRIPTION("MIPI-CSI2 support for FIMC driver"); +MODULE_LICENSE("GPL"); |