diff options
Diffstat (limited to 'drivers/media/platform/imx8/mxc-mipi-csi2_yav.c')
-rw-r--r-- | drivers/media/platform/imx8/mxc-mipi-csi2_yav.c | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c b/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c new file mode 100644 index 000000000000..9fa951b50b50 --- /dev/null +++ b/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c @@ -0,0 +1,735 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2017 NXP + */ +#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/mfd/syscon.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/regmap.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 "mxc-mipi-csi2.h" +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +#define MXC_MIPI_CSI2_YAV_DRIVER_NAME "mxc-mipi-csi2_yav" +#define MXC_MIPI_CSI2_YAV_SUBDEV_NAME MXC_MIPI_CSI2_DRIVER_NAME + +#define GPR_CSI2_1_RX_ENABLE BIT(13) +#define GPR_CSI2_1_VID_INTFC_ENB BIT(12) +#define GPR_CSI2_1_PD_RX BIT(11) +#define GPR_CSI2_1_HSEL BIT(10) +#define GPR_CSI2_1_AUTO_PD_EN BIT(9) +#define GPR_CSI2_1_CONT_CLK_MODE BIT(8) +#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3F) << 2) +#define GPR_CSI2_1_RX_RCAL (3) + +static u8 rxhs_settle[2] = { 0x14, 0x9 }; + +static struct mxc_mipi_csi2_dev *sd_to_mxc_mipi_csi2_dev(struct v4l2_subdev + *sdev) +{ + return container_of(sdev, struct mxc_mipi_csi2_dev, sd); +} + +#ifdef debug +static void mxc_mipi_csi2_reg_dump(struct mxc_mipi_csi2_dev *csi2dev) +{ + printk("MIPI CSI2 HC register dump, mipi csi%d\n", csi2dev->id); + printk("MIPI CSI2 HC num of lanes 0x100 = 0x%x\n", + readl(csi2dev->base_regs + 0x100)); + printk("MIPI CSI2 HC dis lanes 0x104 = 0x%x\n", + readl(csi2dev->base_regs + 0x104)); + printk("MIPI CSI2 HC BIT ERR 0x108 = 0x%x\n", + readl(csi2dev->base_regs + 0x108)); + printk("MIPI CSI2 HC IRQ STATUS 0x10C = 0x%x\n", + readl(csi2dev->base_regs + 0x10C)); + printk("MIPI CSI2 HC IRQ MASK 0x110 = 0x%x\n", + readl(csi2dev->base_regs + 0x110)); + printk("MIPI CSI2 HC ULPS STATUS 0x114 = 0x%x\n", + readl(csi2dev->base_regs + 0x114)); + printk("MIPI CSI2 HC DPHY ErrSotHS 0x118 = 0x%x\n", + readl(csi2dev->base_regs + 0x118)); + printk("MIPI CSI2 HC DPHY ErrSotSync 0x11c = 0x%x\n", + readl(csi2dev->base_regs + 0x11c)); + printk("MIPI CSI2 HC DPHY ErrEsc 0x120 = 0x%x\n", + readl(csi2dev->base_regs + 0x120)); + printk("MIPI CSI2 HC DPHY ErrSyncEsc 0x124 = 0x%x\n", + readl(csi2dev->base_regs + 0x124)); + printk("MIPI CSI2 HC DPHY ErrControl 0x128 = 0x%x\n", + readl(csi2dev->base_regs + 0x128)); + printk("MIPI CSI2 HC DISABLE_PAYLOAD 0x12C = 0x%x\n", + readl(csi2dev->base_regs + 0x12C)); + printk("MIPI CSI2 HC DISABLE_PAYLOAD 0x130 = 0x%x\n", + readl(csi2dev->base_regs + 0x130)); + printk("MIPI CSI2 HC IGNORE_VC 0x180 = 0x%x\n", + readl(csi2dev->base_regs + 0x180)); + printk("MIPI CSI2 HC VID_VC 0x184 = 0x%x\n", + readl(csi2dev->base_regs + 0x184)); + printk("MIPI CSI2 HC FIFO_SEND_LEVEL 0x188 = 0x%x\n", + readl(csi2dev->base_regs + 0x188)); + printk("MIPI CSI2 HC VID_VSYNC 0x18C = 0x%x\n", + readl(csi2dev->base_regs + 0x18C)); + printk("MIPI CSI2 HC VID_SYNC_FP 0x190 = 0x%x\n", + readl(csi2dev->base_regs + 0x190)); + printk("MIPI CSI2 HC VID_HSYNC 0x194 = 0x%x\n", + readl(csi2dev->base_regs + 0x194)); + printk("MIPI CSI2 HC VID_HSYNC_BP 0x198 = 0x%x\n", + readl(csi2dev->base_regs + 0x198)); +} +#else +static void mxc_mipi_csi2_reg_dump(struct mxc_mipi_csi2_dev *csi2dev) +{ +} +#endif + +static int mxc_mipi_csi2_phy_reset(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *node; + phandle phandle; + u32 out_val[3]; + int ret; + + ret = of_property_read_u32_array(np, "csis-phy-reset", out_val, 3); + if (ret) { + dev_info(dev, "no csis-hw-reset property found\n"); + } else { + phandle = *out_val; + + node = of_find_node_by_phandle(phandle); + if (!node) { + dev_dbg(dev, "not find src node by phandle\n"); + ret = PTR_ERR(node); + } + csi2dev->hw_reset.src = syscon_node_to_regmap(node); + if (IS_ERR(csi2dev->hw_reset.src)) { + dev_err(dev, "failed to get src regmap\n"); + ret = PTR_ERR(csi2dev->hw_reset.src); + } + of_node_put(node); + if (ret < 0) + return ret; + + csi2dev->hw_reset.req_src = out_val[1]; + csi2dev->hw_reset.rst_val = out_val[2]; + + /* reset mipi phy */ + regmap_update_bits(csi2dev->hw_reset.src, + csi2dev->hw_reset.req_src, + csi2dev->hw_reset.rst_val, + csi2dev->hw_reset.rst_val); + msleep(20); + } + + return ret; +} + +static int mxc_mipi_csi2_phy_gpr(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *node; + phandle phandle; + u32 out_val[2]; + int ret; + + ret = of_property_read_u32_array(np, "phy-gpr", out_val, 2); + if (ret) { + dev_dbg(dev, "no phy-gpr property found\n"); + } else { + phandle = *out_val; + + node = of_find_node_by_phandle(phandle); + if (!node) { + dev_dbg(dev, "not find gpr node by phandle\n"); + ret = PTR_ERR(node); + } + csi2dev->phy_gpr.gpr = syscon_node_to_regmap(node); + if (IS_ERR(csi2dev->phy_gpr.gpr)) { + dev_err(dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(csi2dev->phy_gpr.gpr); + } + of_node_put(node); + if (ret < 0) + return ret; + + csi2dev->phy_gpr.req_src = out_val[1]; + + regmap_update_bits(csi2dev->phy_gpr.gpr, + csi2dev->phy_gpr.req_src, + 0x3FFF, + GPR_CSI2_1_RX_ENABLE | + GPR_CSI2_1_VID_INTFC_ENB | + GPR_CSI2_1_HSEL | + GPR_CSI2_1_CONT_CLK_MODE | + GPR_CSI2_1_S_PRG_RXHS_SETTLE(csi2dev-> + hs_settle)); + } + + return ret; +} + +static void mxc_mipi_csi2_enable(struct mxc_mipi_csi2_dev *csi2dev) +{ + mxc_mipi_csi2_phy_gpr(csi2dev); +} + +static void mxc_mipi_csi2_disable(struct mxc_mipi_csi2_dev *csi2dev) +{ + /* Disable Data lanes */ + writel(0xf, csi2dev->base_regs + CSI2RX_CFG_DISABLE_DATA_LANES); +} + +static void mxc_mipi_csi2_hc_config(struct mxc_mipi_csi2_dev *csi2dev) +{ + u32 val0, val1; + u32 i; + + val0 = 0; + + /* Lanes */ + writel(csi2dev->num_lanes - 1, + csi2dev->base_regs + CSI2RX_CFG_NUM_LANES); + + for (i = 0; i < csi2dev->num_lanes; i++) + val0 |= (1 << (csi2dev->data_lanes[i] - 1)); + + val1 = 0xF & ~val0; + writel(val1, csi2dev->base_regs + CSI2RX_CFG_DISABLE_DATA_LANES); + + /* Mask interrupt */ + writel(0x1FF, csi2dev->base_regs + CSI2RX_IRQ_MASK); + + writel(1, csi2dev->base_regs + 0x180); + /* vid_vc */ + writel(1, csi2dev->base_regs + 0x184); + writel(csi2dev->send_level, csi2dev->base_regs + 0x188); +} + +static int mipi_csi2_clk_init(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + + csi2dev->clk_core = devm_clk_get(dev, "clk_core"); + if (IS_ERR(csi2dev->clk_core)) { + dev_err(dev, "failed to get csi core clk\n"); + return PTR_ERR(csi2dev->clk_core); + } + + csi2dev->clk_esc = devm_clk_get(dev, "clk_esc"); + if (IS_ERR(csi2dev->clk_esc)) { + dev_err(dev, "failed to get csi esc clk\n"); + return PTR_ERR(csi2dev->clk_esc); + } + + csi2dev->clk_pxl = devm_clk_get(dev, "clk_pxl"); + if (IS_ERR(csi2dev->clk_pxl)) { + dev_err(dev, "failed to get csi pixel link clk\n"); + return PTR_ERR(csi2dev->clk_pxl); + } + + return 0; +} + +static int mipi_csi2_clk_enable(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + int ret; + + ret = clk_prepare_enable(csi2dev->clk_core); + if (ret < 0) { + dev_err(dev, "%s, pre clk_core error\n", __func__); + return ret; + } + ret = clk_prepare_enable(csi2dev->clk_esc); + if (ret < 0) { + dev_err(dev, "%s, prepare clk_esc error\n", __func__); + return ret; + } + ret = clk_prepare_enable(csi2dev->clk_pxl); + if (ret < 0) { + dev_err(dev, "%s, prepare clk_pxl error\n", __func__); + return ret; + } + return ret; +} + +static void mipi_csi2_clk_disable(struct mxc_mipi_csi2_dev *csi2dev) +{ + clk_disable_unprepare(csi2dev->clk_core); + clk_disable_unprepare(csi2dev->clk_esc); + clk_disable_unprepare(csi2dev->clk_pxl); +} + +static int mipi_csi2_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +/* + * V4L2 subdev operations + */ +static int mipi_csi2_s_power(struct v4l2_subdev *sd, int on) +{ + return 0; +} + +static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct device *dev = &csi2dev->pdev->dev; + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + int ret = 0; + + dev_dbg(&csi2dev->pdev->dev, "%s: %d, csi2dev: 0x%x\n", + __func__, enable, csi2dev->flags); + + if (enable) { + if (!csi2dev->running) { + pm_runtime_get_sync(dev); + mxc_mipi_csi2_phy_reset(csi2dev); + mxc_mipi_csi2_hc_config(csi2dev); + mxc_mipi_csi2_enable(csi2dev); + mxc_mipi_csi2_reg_dump(csi2dev); + } + v4l2_subdev_call(sensor_sd, video, s_stream, true); + csi2dev->running++; + + } else { + + v4l2_subdev_call(sensor_sd, video, s_stream, false); + csi2dev->running--; + if (!csi2dev->running) { + pm_runtime_put(dev); + mxc_mipi_csi2_disable(csi2dev); + } + } + + return ret; +} + +static int mipi_csis_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + return v4l2_subdev_call(sensor_sd, pad, enum_frame_size, NULL, fse); +} + +static int mipi_csis_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum + *fie) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + return v4l2_subdev_call(sensor_sd, pad, enum_frame_interval, NULL, fie); +} + +static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + return v4l2_subdev_call(sensor_sd, pad, enum_mbus_code, NULL, code); +} + +static int mipi_csi2_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + if (fmt->pad) + return -EINVAL; + + return v4l2_subdev_call(sensor_sd, pad, get_fmt, NULL, fmt); +} + +static int mipi_csi2_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + if (fmt->pad) + return -EINVAL; + + if (fmt->format.width * fmt->format.height > 720 * 480) { + csi2dev->hs_settle = rxhs_settle[1]; + } else { + csi2dev->hs_settle = rxhs_settle[0]; + } + csi2dev->send_level = 64; + + return v4l2_subdev_call(sensor_sd, pad, set_fmt, NULL, fmt); +} + +static int mipi_csis_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + return v4l2_subdev_call(sensor_sd, video, s_parm, a); +} + +static int mipi_csis_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) +{ + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd; + + return v4l2_subdev_call(sensor_sd, video, g_parm, a); +} + +static const struct v4l2_subdev_internal_ops mipi_csi2_sd_internal_ops = { + .open = mipi_csi2_open, +}; + +static struct v4l2_subdev_pad_ops mipi_csi2_pad_ops = { + .enum_frame_size = mipi_csis_enum_framesizes, + .enum_frame_interval = mipi_csis_enum_frameintervals, + .enum_mbus_code = mipi_csis_enum_mbus_code, + .get_fmt = mipi_csi2_get_fmt, + .set_fmt = mipi_csi2_set_fmt, +}; + +static struct v4l2_subdev_core_ops mipi_csi2_core_ops = { + .s_power = mipi_csi2_s_power, +}; + +static struct v4l2_subdev_video_ops mipi_csi2_video_ops = { + .s_stream = mipi_csi2_s_stream, + + .s_parm = mipi_csis_s_parm, + .g_parm = mipi_csis_g_parm, +}; + +static struct v4l2_subdev_ops mipi_csi2_subdev_ops = { + .core = &mipi_csi2_core_ops, + .video = &mipi_csi2_video_ops, + .pad = &mipi_csi2_pad_ops, +}; + +static int mipi_csi2_parse_dt(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + struct device_node *node = dev->of_node; + struct v4l2_fwnode_endpoint endpoint; + u32 i; + + csi2dev->id = of_alias_get_id(node, "csi"); + + csi2dev->vchannel = of_property_read_bool(node, "virtual-channel"); + + node = of_graph_get_next_endpoint(node, NULL); + if (!node) { + dev_err(dev, "No port node\n"); + return -EINVAL; + } + + /* Get port node */ + memset(&endpoint, 0x0, sizeof(endpoint)); + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint); + + csi2dev->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + for (i = 0; i < csi2dev->num_lanes; i++) + csi2dev->data_lanes[i] = endpoint.bus.mipi_csi2.data_lanes[i]; + + of_node_put(node); + + return 0; +} + +static inline struct mxc_mipi_csi2_dev +*notifier_to_mipi_dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct mxc_mipi_csi2_dev, subdev_notifier); +} + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct mxc_mipi_csi2_dev *csi2dev = notifier_to_mipi_dev(notifier); + + if (subdev == NULL) + return -EINVAL; + + /* Find platform data for this sensor subdev */ + if (csi2dev->fwnode == of_fwnode_handle(subdev->dev->of_node)) + csi2dev->sensor_sd = subdev; + + v4l2_info(&csi2dev->v4l2_dev, "Registered sensor subdevice: %s\n", + subdev->name); + + return 0; +} + +static const struct v4l2_async_notifier_operations subdev_notifier_ops = { + .bound = subdev_notifier_bound, +}; + +static int mipi_csis_subdev_host(struct mxc_mipi_csi2_dev *csi2dev) +{ + struct device *dev = &csi2dev->pdev->dev; + struct device_node *parent = dev->of_node; + struct device_node *node, *port, *rem; + struct v4l2_async_subdev *asd; + int ret; + + v4l2_async_notifier_init(&csi2dev->subdev_notifier); + + /* Attach sensors linked to csi receivers */ + for_each_available_child_of_node(parent, node) { + if (of_node_cmp(node->name, "port")) + continue; + + /* The csi node can have only port subnode. */ + port = of_get_next_child(node, NULL); + if (!port) + continue; + rem = of_graph_get_remote_port_parent(port); + of_node_put(port); + if (rem == NULL) { + v4l2_info(&csi2dev->v4l2_dev, + "Remote device at %s not found\n", + port->full_name); + return -1; + } else + v4l2_info(&csi2dev->v4l2_dev, + "Remote device at %s XXX found\n", + port->full_name); + + csi2dev->fwnode = of_fwnode_handle(rem); + asd = v4l2_async_notifier_add_fwnode_subdev( + &csi2dev->subdev_notifier, + csi2dev->fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + dev_err(dev, "failed to add subdev to a notifier\n"); + of_node_put(rem); + return PTR_ERR(asd); + } + of_node_put(rem); + + break; + } + + csi2dev->subdev_notifier.v4l2_dev = &csi2dev->v4l2_dev; + csi2dev->subdev_notifier.ops = &subdev_notifier_ops; + + ret = v4l2_async_notifier_register(&csi2dev->v4l2_dev, + &csi2dev->subdev_notifier); + if (ret) + dev_err(dev, + "Error register async notifier regoster, ret %d\n", + ret); + + return ret; +} + +static int mipi_csi2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *mem_res; + struct mxc_mipi_csi2_dev *csi2dev; + int ret = -ENOMEM; + + dev_info(&pdev->dev, "%s\n", __func__); + csi2dev = devm_kzalloc(dev, sizeof(*csi2dev), GFP_KERNEL); + if (!csi2dev) + return -ENOMEM; + + csi2dev->pdev = pdev; + + ret = mipi_csi2_parse_dt(csi2dev); + if (ret < 0) + return -EINVAL; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csi2dev->base_regs = devm_ioremap_resource(dev, mem_res); + if (IS_ERR(csi2dev->base_regs)) { + dev_err(dev, "Failed to get mipi csi2 HC register\n"); + return PTR_ERR(csi2dev->base_regs); + } + + ret = mipi_csi2_clk_init(csi2dev); + if (ret < 0) + return -EINVAL; + + v4l2_subdev_init(&csi2dev->sd, &mipi_csi2_subdev_ops); + + csi2dev->sd.owner = THIS_MODULE; + snprintf(csi2dev->sd.name, sizeof(csi2dev->sd.name), "%s.%d", + MXC_MIPI_CSI2_YAV_SUBDEV_NAME, csi2dev->id); + + csi2dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + csi2dev->sd.dev = &pdev->dev; + + /* First register a v4l2 device */ + ret = v4l2_device_register(dev, &csi2dev->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Unable to register v4l2 device.\n"); + return -EINVAL; + } + ret = v4l2_async_register_subdev(&csi2dev->sd); + if (ret < 0) { + dev_err(&pdev->dev, "%s--Async register faialed, ret=%d\n", + __func__, ret); + goto e_v4l_dev; + } + + ret = mipi_csis_subdev_host(csi2dev); + if (ret < 0) + goto e_clkdis; + + /* This allows to retrieve the platform device id by the host driver */ + v4l2_set_subdevdata(&csi2dev->sd, pdev); + + /* .. and a pointer to the subdev. */ + platform_set_drvdata(pdev, csi2dev); + + ret = mipi_csi2_clk_enable(csi2dev); + if (ret < 0) + goto e_clkdis; + + dev_info(&pdev->dev, "lanes: %d, name: %s\n", + csi2dev->num_lanes, csi2dev->sd.name); + + csi2dev->running = 0; + csi2dev->flags = MXC_MIPI_CSI2_PM_POWERED; + pm_runtime_enable(&pdev->dev); + mipi_csi2_clk_disable(csi2dev); + + return 0; + +e_clkdis: + v4l2_async_unregister_subdev(&csi2dev->sd); +e_v4l_dev: + v4l2_device_unregister(&csi2dev->v4l2_dev); + return ret; +} + +static int mipi_csi2_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd); + + v4l2_async_notifier_cleanup(&csi2dev->subdev_notifier); + mipi_csi2_clk_disable(csi2dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused mipi_csi2_pm_runtime_resume(struct device *dev) +{ + struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev); + int ret; + + ret = mipi_csi2_clk_enable(csi2dev); + if (ret < 0) { + dev_info(dev, "%s:%d fail\n", __func__, __LINE__); + return -EAGAIN; + } + + return 0; +} + +static int __maybe_unused mipi_csi2_runtime_pm_suspend(struct device *dev) +{ + struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev); + + mipi_csi2_clk_disable(csi2dev); + + return 0; +} + +static int __maybe_unused mipi_csi2_pm_suspend(struct device *dev) +{ + struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev); + + if (csi2dev->flags & MXC_MIPI_CSI2_PM_SUSPENDED) + return 0; + + if (csi2dev->running) { + dev_warn(dev, "running, prevent entering suspend.\n"); + return -EAGAIN; + } + csi2dev->flags &= ~MXC_MIPI_CSI2_PM_POWERED; + csi2dev->flags |= MXC_MIPI_CSI2_PM_SUSPENDED; + + return 0; +} + +static int __maybe_unused mipi_csi2_pm_resume(struct device *dev) +{ + struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev); + + if (csi2dev->flags & MXC_MIPI_CSI2_PM_POWERED) + return 0; + + csi2dev->flags |= MXC_MIPI_CSI2_PM_POWERED; + csi2dev->flags &= ~MXC_MIPI_CSI2_PM_SUSPENDED; + + return 0; +} + +static const struct dev_pm_ops mipi_csi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mipi_csi2_pm_suspend, mipi_csi2_pm_resume) + SET_RUNTIME_PM_OPS(mipi_csi2_runtime_pm_suspend, + mipi_csi2_pm_runtime_resume, + NULL) +}; + +static const struct of_device_id mipi_csi2_of_match[] = { + {.compatible = "fsl,mxc-mipi-csi2_yav",}, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, mipi_csi2_of_match); + +static struct platform_driver mipi_csi2_driver = { + .driver = { + .name = MXC_MIPI_CSI2_YAV_DRIVER_NAME, + .of_match_table = mipi_csi2_of_match, + .pm = &mipi_csi_pm_ops, + }, + .probe = mipi_csi2_probe, + .remove = mipi_csi2_remove, +}; + +module_platform_driver(mipi_csi2_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC MIPI CSI2 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" MXC_MIPI_CSI2_YAV_DRIVER_NAME); |