/* * Copyright 2018 NXP */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mxc-parallel-csi.h" static int debug; module_param(debug, int, 0644); MODULE_PARM_DESC(debug, "Debug level (0-2)"); static int format; module_param(format, int, 0644); MODULE_PARM_DESC(format, "Format level (0-2)"); #ifdef DEBUG static void mxc_pcsi_regs_dump(struct mxc_parallel_csi_dev *pcsidev) { struct device *dev = &pcsidev->pdev->dev; dev_dbg(dev, "HW_IF_CTRL_REG = 0x%08x\n", readl(pcsidev->csr_regs + IF_CTRL_REG)); dev_dbg(dev, "HW_CSI_CTRL_REG = 0x%08x\n", readl(pcsidev->csr_regs + CSI_CTRL_REG)); dev_dbg(dev, "HW_CSI_STATUS = 0x%08x\n", readl(pcsidev->csr_regs + CSI_STATUS)); dev_dbg(dev, "HW_CSI_CTRL_REG1 = 0x%08x\n", readl(pcsidev->csr_regs + CSI_CTRL_REG1)); } #else static void mxc_pcsi_regs_dump(struct mxc_parallel_csi_dev *pcsidev) { } #endif static struct mxc_parallel_csi_dev *sd_to_mxc_pcsi_dev(struct v4l2_subdev *sdev) { return container_of(sdev, struct mxc_parallel_csi_dev, sd); } static int mxc_pcsi_clk_get(struct mxc_parallel_csi_dev *pcsidev) { struct device *dev = &pcsidev->pdev->dev; pcsidev->clk_pixel = devm_clk_get(dev, "pixel"); if (IS_ERR(pcsidev->clk_pixel)) { dev_info(dev, "%s failed to get parallel csi pixel clk\n", __func__); return PTR_ERR(pcsidev->clk_pixel); } pcsidev->clk_ipg = devm_clk_get(dev, "ipg"); if (IS_ERR(pcsidev->clk_ipg)) { dev_info(dev, "%s failed to get parallel ipg pixel clk\n", __func__); return PTR_ERR(pcsidev->clk_ipg); } return 0; } static int mxc_pcsi_clk_enable(struct mxc_parallel_csi_dev *pcsidev) { struct device *dev = &pcsidev->pdev->dev; int ret; if (pcsidev->clk_enable) return 0; ret = clk_prepare_enable(pcsidev->clk_pixel); if (ret < 0) { dev_info(dev, "%s, enable pixel clk error\n", __func__); return ret; } ret = clk_prepare_enable(pcsidev->clk_ipg); if (ret < 0) { dev_info(dev, "%s, enable ipg clk error\n", __func__); return ret; } pcsidev->clk_enable = true; return 0; } static void mxc_pcsi_clk_disable(struct mxc_parallel_csi_dev *pcsidev) { if (!pcsidev->clk_enable) return; clk_disable_unprepare(pcsidev->clk_pixel); clk_disable_unprepare(pcsidev->clk_ipg); pcsidev->clk_enable = false; } static void mxc_pcsi_sw_reset(struct mxc_parallel_csi_dev *pcsidev) { u32 val; /* Softwaret Reset */ val = CSI_CTRL_REG_SOFTRST; writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET); msleep(1); writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR); } static void mxc_pcsi_csr_config(struct mxc_parallel_csi_dev *pcsidev) { u32 val; /* Software Reset */ mxc_pcsi_sw_reset(pcsidev); /* Config PL Data Type */ val = IF_CTRL_REG_DATA_TYPE(DATA_TYPE_OUT_YUV444); writel(val, pcsidev->csr_regs + IF_CTRL_REG_SET); /* Enable sync Force */ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN); writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET); /* Enable Pixel Link */ val = IF_CTRL_REG_PL_ENABLE; writel(val , pcsidev->csr_regs + IF_CTRL_REG_SET); /* Enable Pixel Link */ val = IF_CTRL_REG_PL_VALID; writel(val , pcsidev->csr_regs + IF_CTRL_REG_SET); /* Config CTRL REG */ val = readl(pcsidev->csr_regs + CSI_CTRL_REG); val |= ( CSI_CTRL_REG_DATA_TYPE_IN(DATA_TYPE_IN_YVYU_8BITS) | CSI_CTRL_REG_HSYNC_POL | CSI_CTRL_REG_MASK_VSYNC_COUNTER(3) | CSI_CTRL_REG_HSYNC_PULSE(2)); if (pcsidev->uv_swap) val |= CSI_CTRL_REG_UV_SWAP_EN; if (pcsidev->mode & GATE_CLOCK_MODE) val |= CSI_CTRL_REG_GCLK_MODE_EN; else if (pcsidev->mode & CCIR_MODE) { val |= (CSI_CTRL_REG_CCIR_EN | CSI_CTRL_REG_CCIR_VSYNC_RESET_EN | CSI_CTRL_REG_CCIR_EXT_VSYNC_EN | CSI_CTRL_REG_CCIR_ECC_ERR_CORRECT_EN); } writel(val, pcsidev->csr_regs + CSI_CTRL_REG); } static void mxc_pcsi_config_ctrl_reg1(struct mxc_parallel_csi_dev *pcsidev) { struct device *dev = &pcsidev->pdev->dev; u32 val; if (pcsidev->format.width <= 0 || pcsidev->format.height <= 0) { dev_dbg(dev, "%s width/height invalid\n", __func__); return; } /* Config Pixel Width */ val = (CSI_CTRL_REG1_PIXEL_WIDTH(pcsidev->format.width - 1) | CSI_CTRL_REG1_VSYNC_PULSE(pcsidev->format.width << 1)); writel(val, pcsidev->csr_regs + CSI_CTRL_REG1); } static void mxc_pcsi_enable_csi(struct mxc_parallel_csi_dev *pcsidev) { u32 val; /* Enable CSI */ val = CSI_CTRL_REG_CSI_EN; writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET); /* Disable SYNC Force */ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN); writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR); } static void mxc_pcsi_disable_csi(struct mxc_parallel_csi_dev *pcsidev) { u32 val; /* Enable Sync Force */ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN); writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET); /* Disable CSI */ val = CSI_CTRL_REG_CSI_EN; writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR); /* Disable Pixel Link */ val = IF_CTRL_REG_PL_VALID | IF_CTRL_REG_PL_ENABLE; writel(val , pcsidev->csr_regs + IF_CTRL_REG_CLR); } static struct media_pad *mxc_pcsi_get_remote_sensor_pad( struct mxc_parallel_csi_dev *pcsidev) { struct v4l2_subdev *subdev = &pcsidev->sd; struct media_pad *sink_pad, *source_pad; int i; while (1) { source_pad = NULL; for (i = 0; i < subdev->entity.num_pads; i++) { sink_pad = &subdev->entity.pads[i]; if (sink_pad->flags & MEDIA_PAD_FL_SINK) { source_pad = media_entity_remote_pad(sink_pad); if (source_pad) break; } } /* return first pad point in the loop */ return source_pad; } if (i == subdev->entity.num_pads) v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return NULL; } static int mxc_pcsi_get_sensor_fmt(struct mxc_parallel_csi_dev *pcsidev) { struct v4l2_mbus_framefmt *mf = &pcsidev->format; struct v4l2_subdev *sen_sd; struct media_pad *source_pad; struct v4l2_subdev_format src_fmt; int ret; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } src_fmt.pad = source_pad->index; ret = v4l2_subdev_call(sen_sd, pad, get_fmt, NULL, &src_fmt); if (ret < 0 && ret != -ENOIOCTLCMD) return -EINVAL; /* Update input frame size and formate */ memcpy(mf, &src_fmt.format, sizeof(struct v4l2_mbus_framefmt)); if (mf->code == MEDIA_BUS_FMT_YUYV8_2X8) pcsidev->uv_swap = 1; dev_dbg(&pcsidev->pdev->dev, "width=%d, height=%d, fmt.code=0x%x\n", mf->width, mf->height, mf->code); return 0; } static int mxc_pcsi_enum_framesizes(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_size_enum *fse) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } return v4l2_subdev_call(sen_sd, pad, enum_frame_size, NULL, fse); } static int mxc_pcsi_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_interval_enum *fie) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } return v4l2_subdev_call(sen_sd, pad, enum_frame_interval, NULL, fie); } static int mxc_pcsi_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct v4l2_mbus_framefmt *mf = &fmt->format; mxc_pcsi_get_sensor_fmt(pcsidev); memcpy(mf, &pcsidev->format, sizeof(struct v4l2_mbus_framefmt)); /* Source/Sink pads crop rectangle size */ return 0; } static int mxc_pcsi_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct v4l2_subdev *sen_sd; struct media_pad *source_pad; int ret; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } fmt->pad = source_pad->index; ret = v4l2_subdev_call(sen_sd, pad, set_fmt, NULL, fmt); if (ret < 0 && ret != -ENOIOCTLCMD) return -EINVAL; return 0; } static int mxc_pcsi_s_power(struct v4l2_subdev *sd, int on) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } return v4l2_subdev_call(sen_sd, core, s_power, on); } static int mxc_pcsi_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } return v4l2_subdev_call(sen_sd, video, s_parm, a); } static int mxc_pcsi_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct media_pad *source_pad; struct v4l2_subdev *sen_sd; /* Get remote source pad */ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev); if (source_pad == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__); return -EINVAL; } /* Get remote source pad subdev */ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity); if (sen_sd == NULL) { v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__); return -EINVAL; } return v4l2_subdev_call(sen_sd, video, g_parm, a); } static int mxc_pcsi_s_stream(struct v4l2_subdev *sd, int enable) { struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd); struct device *dev = &pcsidev->pdev->dev; dev_dbg(dev, "%s: %d, pcsidev: 0x%d\n", __func__, __LINE__, enable); if (enable) { pm_runtime_get_sync(dev); if (!pcsidev->running) { mxc_pcsi_get_sensor_fmt(pcsidev); mxc_pcsi_csr_config(pcsidev); mxc_pcsi_config_ctrl_reg1(pcsidev); mxc_pcsi_enable_csi(pcsidev); mxc_pcsi_regs_dump(pcsidev); } pcsidev->running++; } else { if (pcsidev->running) mxc_pcsi_disable_csi(pcsidev); pcsidev->running--; pm_runtime_put(dev); } return 0; } static int mxc_pcsi_link_setup(struct media_entity *entity, const struct media_pad *local, const struct media_pad *remote, u32 flags) { struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); struct platform_device *pdev = v4l2_get_subdevdata(sd); if (local->flags & MEDIA_PAD_FL_SOURCE) { switch (local->index) { case MXC_PARALLEL_CSI_PAD_SOURCE: break; default: dev_err(&pdev->dev, "%s invalid source pad\n", __func__); return -EINVAL; } } else if (local->flags & MEDIA_PAD_FL_SINK) { switch (local->index) { case MXC_PARALLEL_CSI_PAD_SINK: break; default: dev_err(&pdev->dev, "%s invalid sink pad\n", __func__); return -EINVAL; } } return 0; } static struct v4l2_subdev_pad_ops pcsi_pad_ops = { .enum_frame_size = mxc_pcsi_enum_framesizes, .enum_frame_interval = mxc_pcsi_enum_frame_interval, .get_fmt = mxc_pcsi_get_fmt, .set_fmt = mxc_pcsi_set_fmt, }; static struct v4l2_subdev_core_ops pcsi_core_ops = { .s_power = mxc_pcsi_s_power, }; static struct v4l2_subdev_video_ops pcsi_video_ops = { .s_parm = mxc_pcsi_s_parm, .g_parm = mxc_pcsi_g_parm, .s_stream = mxc_pcsi_s_stream, }; static struct v4l2_subdev_ops pcsi_subdev_ops = { .core = &pcsi_core_ops, .video = &pcsi_video_ops, .pad = &pcsi_pad_ops, }; static const struct media_entity_operations mxc_pcsi_sd_media_ops = { .link_setup = mxc_pcsi_link_setup, }; static int mxc_parallel_csi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *mem_res; struct mxc_parallel_csi_dev *pcsidev; int ret; pcsidev = devm_kzalloc(dev, sizeof(*pcsidev), GFP_KERNEL); if (!pcsidev) return -ENOMEM; pcsidev->pdev = pdev; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pcsidev->csr_regs = devm_ioremap_resource(dev, mem_res); if (IS_ERR(pcsidev->csr_regs)) { dev_dbg(dev, "Failed to get parallel CSI CSR register\n"); return PTR_ERR(pcsidev->csr_regs); } ret = mxc_pcsi_clk_get(pcsidev); if (ret < 0) return ret; v4l2_subdev_init(&pcsidev->sd, &pcsi_subdev_ops); pcsidev->mode = GATE_CLOCK_MODE; pcsidev->sd.owner = THIS_MODULE; sprintf(pcsidev->sd.name, "%s", MXC_PARALLEL_CSI_SUBDEV_NAME); pcsidev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; pcsidev->sd.entity.function = MEDIA_ENT_F_IO_V4L; pcsidev->sd.dev = dev; pcsidev->pads[MXC_PARALLEL_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK; pcsidev->pads[MXC_PARALLEL_CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&pcsidev->sd.entity, MXC_PARALLEL_CSI_PADS_NUM, pcsidev->pads); if (ret < 0) goto e_clkdis; pcsidev->sd.entity.ops = &mxc_pcsi_sd_media_ops; v4l2_set_subdevdata(&pcsidev->sd, pdev); platform_set_drvdata(pdev, pcsidev); pcsidev->running = 0; pm_runtime_enable(dev); dev_info(dev, "%s probe successfully\n", __func__); return 0; e_clkdis: media_entity_cleanup(&pcsidev->sd.entity); return ret; } static int mxc_parallel_csi_remove(struct platform_device *pdev) { struct mxc_parallel_csi_dev *pcsidev = (struct mxc_parallel_csi_dev *)platform_get_drvdata(pdev); media_entity_cleanup(&pcsidev->sd.entity); pm_runtime_disable(&pdev->dev); return 0; } static int __maybe_unused parallel_csi_pm_suspend(struct device *dev) { return pm_runtime_force_suspend(dev); } static int __maybe_unused parallel_csi_pm_resume(struct device *dev) { return pm_runtime_force_resume(dev); } static int __maybe_unused parallel_csi_runtime_suspend(struct device *dev) { struct mxc_parallel_csi_dev *pcsidev = dev_get_drvdata(dev); mxc_pcsi_clk_disable(pcsidev); return 0; } static int __maybe_unused parallel_csi_runtime_resume(struct device *dev) { struct mxc_parallel_csi_dev *pcsidev = dev_get_drvdata(dev); int ret; ret = mxc_pcsi_clk_enable(pcsidev); if (ret < 0) return ret; return 0; } static const struct dev_pm_ops parallel_csi_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(parallel_csi_pm_suspend, parallel_csi_pm_resume) SET_RUNTIME_PM_OPS(parallel_csi_runtime_suspend, parallel_csi_runtime_resume, NULL) }; static const struct of_device_id parallel_csi_of_match[] = { { .compatible = "fsl,mxc-parallel-csi",}, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, parallel_csi_of_match); static struct platform_driver parallel_csi_driver = { .driver = { .name = MXC_PARALLEL_CSI_DRIVER_NAME, .of_match_table = parallel_csi_of_match, .pm = ¶llel_csi_pm_ops, }, .probe = mxc_parallel_csi_probe, .remove = mxc_parallel_csi_remove, }; module_platform_driver(parallel_csi_driver); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("MXC PARALLEL CSI driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" MXC_PARALLEL_CSI_DRIVER_NAME);