From 5a43ed0df4ba74bc817ac643882341644025329d Mon Sep 17 00:00:00 2001 From: Robby Cai Date: Fri, 27 Sep 2019 21:11:01 +0800 Subject: [PATCH] media: mipi csi: add IPU CSI driver add IPU MIPI CSI driver, used by imx6qdl Signed-off-by: Robby Cai --- drivers/mxc/Kconfig | 1 + drivers/mxc/Makefile | 1 + drivers/mxc/mipi/Kconfig | 14 + drivers/mxc/mipi/Makefile | 4 + drivers/mxc/mipi/mxc_mipi_csi2.c | 528 +++++++++++++++++++++++++++++++ drivers/mxc/mipi/mxc_mipi_csi2.h | 34 ++ include/linux/mipi_csi2.h | 81 +++++ 7 files changed, 663 insertions(+) create mode 100644 drivers/mxc/Kconfig create mode 100644 drivers/mxc/Makefile create mode 100644 drivers/mxc/mipi/Kconfig create mode 100644 drivers/mxc/mipi/Makefile create mode 100644 drivers/mxc/mipi/mxc_mipi_csi2.c create mode 100644 drivers/mxc/mipi/mxc_mipi_csi2.h create mode 100644 include/linux/mipi_csi2.h diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..88efc69c1880 --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1 @@ +source "drivers/mxc/mipi/Kconfig" diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..6b309ddb227e --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_MIPI_CSI2) += mipi/ diff --git a/drivers/mxc/mipi/Kconfig b/drivers/mxc/mipi/Kconfig new file mode 100644 index 000000000000..7e002dde8f47 --- /dev/null +++ b/drivers/mxc/mipi/Kconfig @@ -0,0 +1,14 @@ +# +# MIPI configuration +# + +menu "MXC MIPI Support" + +config MXC_MIPI_CSI2 + tristate "MIPI CSI2 support" + depends on (SOC_IMX6 || SOC_IMX7D) + default n + ---help--- + Say Y to get the MIPI CSI2 support. + +endmenu diff --git a/drivers/mxc/mipi/Makefile b/drivers/mxc/mipi/Makefile new file mode 100644 index 000000000000..98cda773bd28 --- /dev/null +++ b/drivers/mxc/mipi/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the mipi interface driver +# +obj-$(CONFIG_MXC_MIPI_CSI2) += mxc_mipi_csi2.o diff --git a/drivers/mxc/mipi/mxc_mipi_csi2.c b/drivers/mxc/mipi/mxc_mipi_csi2.c new file mode 100644 index 000000000000..62b77a117a68 --- /dev/null +++ b/drivers/mxc/mipi/mxc_mipi_csi2.c @@ -0,0 +1,528 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2019 NXP + */ + +#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"); diff --git a/drivers/mxc/mipi/mxc_mipi_csi2.h b/drivers/mxc/mipi/mxc_mipi_csi2.h new file mode 100644 index 000000000000..1a3c49e2ba10 --- /dev/null +++ b/drivers/mxc/mipi/mxc_mipi_csi2.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2019 NXP + */ + +#ifndef __MXC_MIPI_CSI2_H__ +#define __MXC_MIPI_CSI2_H__ + +#ifdef DEBUG +#define mipi_dbg(fmt, ...) \ + printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) +#else +#define mipi_dbg(fmt, ...) +#endif + +/* driver private data */ +struct mipi_csi2_info { + bool mipi_en; + int ipu_id; + unsigned int csi_id; + unsigned int v_channel; + unsigned int lanes; + unsigned int datatype; + struct clk *cfg_clk; + struct clk *dphy_clk; + struct clk *pixel_clk; + void __iomem *mipi_csi2_base; + struct platform_device *pdev; + + struct mutex mutex_lock; +}; + +#endif diff --git a/include/linux/mipi_csi2.h b/include/linux/mipi_csi2.h new file mode 100644 index 000000000000..d750ed9b02d5 --- /dev/null +++ b/include/linux/mipi_csi2.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2019 NXP + */ + +#ifndef __INCLUDE_MIPI_CSI2_H +#define __INCLUDE_MIPI_CSI2_H + +/* MIPI CSI2 registers */ +#define MIPI_CSI2_REG(offset) (offset) + +#define MIPI_CSI2_VERSION MIPI_CSI2_REG(0x000) +#define MIPI_CSI2_N_LANES MIPI_CSI2_REG(0x004) +#define MIPI_CSI2_PHY_SHUTDOWNZ MIPI_CSI2_REG(0x008) +#define MIPI_CSI2_DPHY_RSTZ MIPI_CSI2_REG(0x00c) +#define MIPI_CSI2_CSI2_RESETN MIPI_CSI2_REG(0x010) +#define MIPI_CSI2_PHY_STATE MIPI_CSI2_REG(0x014) +#define MIPI_CSI2_DATA_IDS_1 MIPI_CSI2_REG(0x018) +#define MIPI_CSI2_DATA_IDS_2 MIPI_CSI2_REG(0x01c) +#define MIPI_CSI2_ERR1 MIPI_CSI2_REG(0x020) +#define MIPI_CSI2_ERR2 MIPI_CSI2_REG(0x024) +#define MIPI_CSI2_MASK1 MIPI_CSI2_REG(0x028) +#define MIPI_CSI2_MASK2 MIPI_CSI2_REG(0x02c) +#define MIPI_CSI2_PHY_TST_CTRL0 MIPI_CSI2_REG(0x030) +#define MIPI_CSI2_PHY_TST_CTRL1 MIPI_CSI2_REG(0x034) +#define MIPI_CSI2_SFT_RESET MIPI_CSI2_REG(0xf00) + +/* mipi data type */ +#define MIPI_DT_YUV420 0x18 /* YYY.../UYVY.... */ +#define MIPI_DT_YUV420_LEGACY 0x1a /* UYY.../VYY... */ +#define MIPI_DT_YUV422 0x1e /* UYVY... */ +#define MIPI_DT_RGB444 0x20 +#define MIPI_DT_RGB555 0x21 +#define MIPI_DT_RGB565 0x22 +#define MIPI_DT_RGB666 0x23 +#define MIPI_DT_RGB888 0x24 +#define MIPI_DT_RAW6 0x28 +#define MIPI_DT_RAW7 0x29 +#define MIPI_DT_RAW8 0x2a +#define MIPI_DT_RAW10 0x2b +#define MIPI_DT_RAW12 0x2c +#define MIPI_DT_RAW14 0x2d + + +struct mipi_csi2_info; +/* mipi csi2 API */ +struct mipi_csi2_info *mipi_csi2_get_info(void); + +bool mipi_csi2_enable(struct mipi_csi2_info *info); + +bool mipi_csi2_disable(struct mipi_csi2_info *info); + +bool mipi_csi2_get_status(struct mipi_csi2_info *info); + +int mipi_csi2_get_bind_ipu(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_get_bind_csi(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_get_virtual_channel(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_set_lanes(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_set_datatype(struct mipi_csi2_info *info, + unsigned int datatype); + +unsigned int mipi_csi2_get_datatype(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_dphy_status(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_get_error1(struct mipi_csi2_info *info); + +unsigned int mipi_csi2_get_error2(struct mipi_csi2_info *info); + +int mipi_csi2_pixelclk_enable(struct mipi_csi2_info *info); + +void mipi_csi2_pixelclk_disable(struct mipi_csi2_info *info); + +int mipi_csi2_reset(struct mipi_csi2_info *info); + +#endif