media: mipi csi: add IPU CSI driver

add IPU MIPI CSI driver, used by imx6qdl

Signed-off-by: Robby Cai <robby.cai@nxp.com>
This commit is contained in:
Robby Cai 2019-09-27 21:11:01 +08:00 committed by Dong Aisheng
parent 5f77e6b267
commit 5a43ed0df4
7 changed files with 663 additions and 0 deletions

1
drivers/mxc/Kconfig Normal file
View File

@ -0,0 +1 @@
source "drivers/mxc/mipi/Kconfig"

1
drivers/mxc/Makefile Normal file
View File

@ -0,0 +1 @@
obj-$(CONFIG_MXC_MIPI_CSI2) += mipi/

14
drivers/mxc/mipi/Kconfig Normal file
View File

@ -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

View File

@ -0,0 +1,4 @@
#
# Makefile for the mipi interface driver
#
obj-$(CONFIG_MXC_MIPI_CSI2) += mxc_mipi_csi2.o

View File

@ -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 <linux/module.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/fsl_devices.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/mipi_csi2.h>
#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");

View File

@ -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

81
include/linux/mipi_csi2.h Normal file
View File

@ -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