/* * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. 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 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mxc_mipi_csi2.h" static struct mipi_csi2_info *gmipi_csi2; void _mipi_csi2_lock(struct mipi_csi2_info *info) { if (!in_irq() && !in_softirq()) mutex_lock(&info->mutex_lock); } void _mipi_csi2_unlock(struct mipi_csi2_info *info) { if (!in_irq() && !in_softirq()) mutex_unlock(&info->mutex_lock); } static inline void mipi_csi2_write(struct mipi_csi2_info *info, unsigned value, unsigned offset) { writel(value, info->mipi_csi2_base + offset); } static inline unsigned int mipi_csi2_read(struct mipi_csi2_info *info, unsigned offset) { return readl(info->mipi_csi2_base + offset); } /*! * This function is called to enable the mipi csi2 interface. * * @param info mipi csi2 hander * @return Returns setted value */ bool mipi_csi2_enable(struct mipi_csi2_info *info) { bool status; _mipi_csi2_lock(info); if (!info->mipi_en) { info->mipi_en = true; clk_prepare_enable(info->cfg_clk); clk_prepare_enable(info->dphy_clk); } else mipi_dbg("mipi csi2 already enabled!\n"); status = info->mipi_en; _mipi_csi2_unlock(info); return status; } EXPORT_SYMBOL(mipi_csi2_enable); /*! * This function is called to disable the mipi csi2 interface. * * @param info mipi csi2 hander * @return Returns setted value */ bool mipi_csi2_disable(struct mipi_csi2_info *info) { bool status; _mipi_csi2_lock(info); if (info->mipi_en) { info->mipi_en = false; clk_disable_unprepare(info->dphy_clk); clk_disable_unprepare(info->cfg_clk); } else mipi_dbg("mipi csi2 already disabled!\n"); status = info->mipi_en; _mipi_csi2_unlock(info); return status; } EXPORT_SYMBOL(mipi_csi2_disable); /*! * This function is called to get mipi csi2 disable/enable status. * * @param info mipi csi2 hander * @return Returns mipi csi2 status */ bool mipi_csi2_get_status(struct mipi_csi2_info *info) { bool status; _mipi_csi2_lock(info); status = info->mipi_en; _mipi_csi2_unlock(info); return status; } EXPORT_SYMBOL(mipi_csi2_get_status); /*! * This function is called to set mipi lanes. * * @param info mipi csi2 hander * @return Returns setted value */ unsigned int mipi_csi2_set_lanes(struct mipi_csi2_info *info) { unsigned int lanes; _mipi_csi2_lock(info); mipi_csi2_write(info, info->lanes - 1, MIPI_CSI2_N_LANES); lanes = mipi_csi2_read(info, MIPI_CSI2_N_LANES); _mipi_csi2_unlock(info); return lanes; } EXPORT_SYMBOL(mipi_csi2_set_lanes); /*! * This function is called to set mipi data type. * * @param info mipi csi2 hander * @return Returns setted value */ unsigned int mipi_csi2_set_datatype(struct mipi_csi2_info *info, unsigned int datatype) { unsigned int dtype; _mipi_csi2_lock(info); info->datatype = datatype; dtype = info->datatype; _mipi_csi2_unlock(info); return dtype; } EXPORT_SYMBOL(mipi_csi2_set_datatype); /*! * This function is called to get mipi data type. * * @param info mipi csi2 hander * @return Returns mipi data type */ unsigned int mipi_csi2_get_datatype(struct mipi_csi2_info *info) { unsigned int dtype; _mipi_csi2_lock(info); dtype = info->datatype; _mipi_csi2_unlock(info); return dtype; } EXPORT_SYMBOL(mipi_csi2_get_datatype); /*! * This function is called to get mipi csi2 dphy status. * * @param info mipi csi2 hander * @return Returns dphy status */ unsigned int mipi_csi2_dphy_status(struct mipi_csi2_info *info) { unsigned int status; _mipi_csi2_lock(info); status = mipi_csi2_read(info, MIPI_CSI2_PHY_STATE); _mipi_csi2_unlock(info); return status; } EXPORT_SYMBOL(mipi_csi2_dphy_status); /*! * This function is called to get mipi csi2 error1 status. * * @param info mipi csi2 hander * @return Returns error1 value */ unsigned int mipi_csi2_get_error1(struct mipi_csi2_info *info) { unsigned int err1; _mipi_csi2_lock(info); err1 = mipi_csi2_read(info, MIPI_CSI2_ERR1); _mipi_csi2_unlock(info); return err1; } EXPORT_SYMBOL(mipi_csi2_get_error1); /*! * This function is called to get mipi csi2 error1 status. * * @param info mipi csi2 hander * @return Returns error1 value */ unsigned int mipi_csi2_get_error2(struct mipi_csi2_info *info) { unsigned int err2; _mipi_csi2_lock(info); err2 = mipi_csi2_read(info, MIPI_CSI2_ERR2); _mipi_csi2_unlock(info); return err2; } EXPORT_SYMBOL(mipi_csi2_get_error2); /*! * This function is called to enable mipi to ipu pixel clock. * * @param info mipi csi2 hander * @return Returns 0 on success or negative error code on fail */ int mipi_csi2_pixelclk_enable(struct mipi_csi2_info *info) { return clk_prepare_enable(info->pixel_clk); } EXPORT_SYMBOL(mipi_csi2_pixelclk_enable); /*! * This function is called to disable mipi to ipu pixel clock. * * @param info mipi csi2 hander * @return Returns 0 on success or negative error code on fail */ void mipi_csi2_pixelclk_disable(struct mipi_csi2_info *info) { clk_disable_unprepare(info->pixel_clk); } EXPORT_SYMBOL(mipi_csi2_pixelclk_disable); /*! * This function is called to power on mipi csi2. * * @param info mipi csi2 hander * @return Returns 0 on success or negative error code on fail */ int mipi_csi2_reset(struct mipi_csi2_info *info) { _mipi_csi2_lock(info); mipi_csi2_write(info, 0x0, MIPI_CSI2_PHY_SHUTDOWNZ); mipi_csi2_write(info, 0x0, MIPI_CSI2_DPHY_RSTZ); mipi_csi2_write(info, 0x0, MIPI_CSI2_CSI2_RESETN); mipi_csi2_write(info, 0x00000001, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL1); mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0x00010044, MIPI_CSI2_PHY_TST_CTRL1); mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0x00000014, MIPI_CSI2_PHY_TST_CTRL1); mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0); mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_PHY_SHUTDOWNZ); mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_DPHY_RSTZ); mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_CSI2_RESETN); _mipi_csi2_unlock(info); return 0; } EXPORT_SYMBOL(mipi_csi2_reset); /*! * This function is called to get mipi csi2 info. * * @return Returns mipi csi2 info struct pointor */ struct mipi_csi2_info *mipi_csi2_get_info(void) { return gmipi_csi2; } EXPORT_SYMBOL(mipi_csi2_get_info); /*! * This function is called to get mipi csi2 bind ipu num. * * @return Returns mipi csi2 bind ipu num */ int mipi_csi2_get_bind_ipu(struct mipi_csi2_info *info) { int ipu_id; _mipi_csi2_lock(info); ipu_id = info->ipu_id; _mipi_csi2_unlock(info); return ipu_id; } EXPORT_SYMBOL(mipi_csi2_get_bind_ipu); /*! * This function is called to get mipi csi2 bind csi num. * * @return Returns mipi csi2 bind csi num */ unsigned int mipi_csi2_get_bind_csi(struct mipi_csi2_info *info) { unsigned int csi_id; _mipi_csi2_lock(info); csi_id = info->csi_id; _mipi_csi2_unlock(info); return csi_id; } EXPORT_SYMBOL(mipi_csi2_get_bind_csi); /*! * This function is called to get mipi csi2 virtual channel. * * @return Returns mipi csi2 virtual channel num */ unsigned int mipi_csi2_get_virtual_channel(struct mipi_csi2_info *info) { unsigned int v_channel; _mipi_csi2_lock(info); v_channel = info->v_channel; _mipi_csi2_unlock(info); return v_channel; } EXPORT_SYMBOL(mipi_csi2_get_virtual_channel); /** * This function is called by the driver framework to initialize the MIPI CSI2 * device. * * @param pdev The device structure for the MIPI CSI2 passed in by the * driver framework. * * @return Returns 0 on success or negative error code on error */ static int mipi_csi2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct resource *res; u32 mipi_csi2_dphy_ver; int ret; gmipi_csi2 = kmalloc(sizeof(struct mipi_csi2_info), GFP_KERNEL); if (!gmipi_csi2) { ret = -ENOMEM; goto alloc_failed; } ret = of_property_read_u32(np, "ipu_id", &(gmipi_csi2->ipu_id)); if (ret) { dev_err(&pdev->dev, "ipu_id missing or invalid\n"); goto err; } ret = of_property_read_u32(np, "csi_id", &(gmipi_csi2->csi_id)); if (ret) { dev_err(&pdev->dev, "csi_id missing or invalid\n"); goto err; } ret = of_property_read_u32(np, "v_channel", &(gmipi_csi2->v_channel)); if (ret) { dev_err(&pdev->dev, "v_channel missing or invalid\n"); goto err; } ret = of_property_read_u32(np, "lanes", &(gmipi_csi2->lanes)); if (ret) { dev_err(&pdev->dev, "lanes missing or invalid\n"); goto err; } if ((gmipi_csi2->ipu_id < 0) || (gmipi_csi2->ipu_id > 1) || (gmipi_csi2->csi_id > 1) || (gmipi_csi2->v_channel > 3) || (gmipi_csi2->lanes > 4)) { dev_err(&pdev->dev, "invalid param for mipi csi2!\n"); ret = -EINVAL; goto err; } /* initialize mutex */ mutex_init(&gmipi_csi2->mutex_lock); /* get mipi csi2 informaiton */ gmipi_csi2->pdev = pdev; gmipi_csi2->mipi_en = false; gmipi_csi2->cfg_clk = devm_clk_get(dev, "cfg_clk"); if (IS_ERR(gmipi_csi2->cfg_clk)) { dev_err(&pdev->dev, "failed to get cfg_clk\n"); ret = PTR_ERR(gmipi_csi2->cfg_clk); goto err; } /* get mipi dphy clk */ gmipi_csi2->dphy_clk = devm_clk_get(dev, "dphy_clk"); if (IS_ERR(gmipi_csi2->dphy_clk)) { dev_err(&pdev->dev, "failed to get dphy pll_ref_clk\n"); ret = PTR_ERR(gmipi_csi2->dphy_clk); goto err; } /* get mipi to ipu pixel clk */ gmipi_csi2->pixel_clk = devm_clk_get(dev, "pixel_clk"); if (IS_ERR(gmipi_csi2->pixel_clk)) { dev_err(&pdev->dev, "failed to get mipi pixel clk\n"); ret = PTR_ERR(gmipi_csi2->pixel_clk); goto err; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENODEV; goto err; } /* mipi register mapping */ gmipi_csi2->mipi_csi2_base = ioremap(res->start, PAGE_SIZE); if (!gmipi_csi2->mipi_csi2_base) { ret = -ENOMEM; goto err; } /* mipi dphy clk enable for register access */ clk_prepare_enable(gmipi_csi2->dphy_clk); /* get mipi csi2 dphy version */ mipi_csi2_dphy_ver = mipi_csi2_read(gmipi_csi2, MIPI_CSI2_VERSION); clk_disable_unprepare(gmipi_csi2->dphy_clk); platform_set_drvdata(pdev, gmipi_csi2); dev_info(&pdev->dev, "i.MX MIPI CSI2 driver probed\n"); dev_info(&pdev->dev, "i.MX MIPI CSI2 dphy version is 0x%x\n", mipi_csi2_dphy_ver); return 0; err: kfree(gmipi_csi2); alloc_failed: dev_err(&pdev->dev, "i.MX MIPI CSI2 driver probed - error\n"); return ret; } static int mipi_csi2_remove(struct platform_device *pdev) { /* unmapping mipi register */ iounmap(gmipi_csi2->mipi_csi2_base); kfree(gmipi_csi2); dev_set_drvdata(&pdev->dev, NULL); return 0; } static const struct of_device_id imx_mipi_csi2_dt_ids[] = { { .compatible = "fsl,imx6q-mipi-csi2", }, { /* sentinel */ } }; static struct platform_driver mipi_csi2_driver = { .driver = { .name = "mxc_mipi_csi2", .of_match_table = imx_mipi_csi2_dt_ids, }, .probe = mipi_csi2_probe, .remove = mipi_csi2_remove, }; static int __init mipi_csi2_init(void) { int err; err = platform_driver_register(&mipi_csi2_driver); if (err) { pr_err("mipi_csi2_driver register failed\n"); return -ENODEV; } pr_info("MIPI CSI2 driver module loaded\n"); return 0; } static void __exit mipi_csi2_cleanup(void) { platform_driver_unregister(&mipi_csi2_driver); } subsys_initcall(mipi_csi2_init); module_exit(mipi_csi2_cleanup); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("i.MX MIPI CSI2 driver"); MODULE_LICENSE("GPL");