| /* |
| * 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 <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/errno.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/irq.h> |
| #include <linux/kernel.h> |
| #include <linux/memory.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_graph.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/videodev2.h> |
| #include <media/v4l2-fwnode.h> |
| #include <media/v4l2-subdev.h> |
| #include <media/v4l2-device.h> |
| #include <soc/imx8/sc/sci.h> |
| #include <dt-bindings/pinctrl/pads-imx8qxp.h> |
| #include <linux/init.h> |
| |
| #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 parallel_csi_pm_suspend(struct device *dev) |
| { |
| return pm_runtime_force_suspend(dev); |
| } |
| |
| static int parallel_csi_pm_resume(struct device *dev) |
| { |
| return pm_runtime_force_resume(dev); |
| } |
| |
| static int 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 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); |