// SPDX-License-Identifier: GPL-2.0 /* * Rockchip CIF Driver * * Copyright (C) 2020 Rockchip Electronics Co., Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dev.h" static inline struct sditf_priv *to_sditf_priv(struct v4l2_subdev *subdev) { return container_of(subdev, struct sditf_priv, sd); } static int sditf_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) { struct sditf_priv *priv = to_sditf_priv(sd); struct rkcif_device *cif_dev = priv->cif_dev; struct v4l2_subdev *sensor_sd; if (!cif_dev->terminal_sensor.sd) rkcif_update_sensor_info(&cif_dev->stream[0]); if (cif_dev->terminal_sensor.sd) { sensor_sd = cif_dev->terminal_sensor.sd; return v4l2_subdev_call(sensor_sd, video, g_frame_interval, fi); } return -EINVAL; } static int sditf_g_mbus_config(struct v4l2_subdev *sd, struct v4l2_mbus_config *config) { struct sditf_priv *priv = to_sditf_priv(sd); struct rkcif_device *cif_dev = priv->cif_dev; struct v4l2_subdev *sensor_sd; if (!cif_dev->active_sensor) rkcif_update_sensor_info(&cif_dev->stream[0]); if (cif_dev->active_sensor) { sensor_sd = cif_dev->active_sensor->sd; return v4l2_subdev_call(sensor_sd, video, g_mbus_config, config); } return -EINVAL; } static int sditf_get_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { struct sditf_priv *priv = to_sditf_priv(sd); struct rkcif_device *cif_dev = priv->cif_dev; struct v4l2_subdev_selection input_sel; int ret = -EINVAL; if (!cif_dev->terminal_sensor.sd) rkcif_update_sensor_info(&cif_dev->stream[0]); if (cif_dev->terminal_sensor.sd) { ret = v4l2_subdev_call(cif_dev->terminal_sensor.sd, pad, get_fmt, NULL, fmt); if (ret) { v4l2_err(&priv->sd, "%s: get sensor format failed\n", __func__); return ret; } input_sel.target = V4L2_SEL_TGT_CROP_BOUNDS; ret = v4l2_subdev_call(cif_dev->terminal_sensor.sd, pad, get_selection, NULL, &input_sel); if (!ret) { fmt->format.width = input_sel.r.width; fmt->format.height = input_sel.r.height; } } return 0; } static int sditf_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { return -EINVAL; } static long sditf_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { struct sditf_priv *priv = to_sditf_priv(sd); struct rkcif_device *cif_dev = priv->cif_dev; struct v4l2_subdev *sensor_sd; if (!cif_dev->terminal_sensor.sd) rkcif_update_sensor_info(&cif_dev->stream[0]); if (cif_dev->terminal_sensor.sd) { sensor_sd = cif_dev->terminal_sensor.sd; return v4l2_subdev_call(sensor_sd, core, ioctl, cmd, arg); } return -EINVAL; } #ifdef CONFIG_COMPAT static long sditf_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd, unsigned long arg) { struct sditf_priv *priv = to_sditf_priv(sd); struct rkcif_device *cif_dev = priv->cif_dev; struct v4l2_subdev *sensor_sd; if (!cif_dev->terminal_sensor.sd) rkcif_update_sensor_info(&cif_dev->stream[0]); if (cif_dev->terminal_sensor.sd) { sensor_sd = cif_dev->terminal_sensor.sd; return v4l2_subdev_call(sensor_sd, core, compat_ioctl32, cmd, arg); } return -EINVAL; } #endif static const struct v4l2_subdev_pad_ops sditf_subdev_pad_ops = { .set_fmt = sditf_get_set_fmt, .get_fmt = sditf_get_set_fmt, .get_selection = sditf_get_selection, }; static const struct v4l2_subdev_video_ops sditf_video_ops = { .g_frame_interval = sditf_g_frame_interval, .g_mbus_config = sditf_g_mbus_config, }; static const struct v4l2_subdev_core_ops sditf_core_ops = { .ioctl = sditf_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = sditf_compat_ioctl32, #endif }; static const struct v4l2_subdev_ops sditf_subdev_ops = { .core = &sditf_core_ops, .video = &sditf_video_ops, .pad = &sditf_subdev_pad_ops, }; static int rkcif_sditf_attach_cifdev(struct sditf_priv *sditf) { struct device_node *np; struct platform_device *pdev; struct rkcif_device *cif_dev; np = of_parse_phandle(sditf->dev->of_node, "rockchip,cif", 0); if (!np || !of_device_is_available(np)) { dev_err(sditf->dev, "failed to get cif dev node\n"); return -ENODEV; } pdev = of_find_device_by_node(np); of_node_put(np); if (!pdev) { dev_err(sditf->dev, "failed to get cif dev from node\n"); return -ENODEV; } cif_dev = platform_get_drvdata(pdev); if (!cif_dev) { dev_err(sditf->dev, "failed attach cif dev\n"); return -EINVAL; } cif_dev->sditf = sditf; sditf->cif_dev = cif_dev; return 0; } static int rkcif_subdev_media_init(struct sditf_priv *priv) { struct rkcif_device *cif_dev = priv->cif_dev; int ret; priv->pads.flags = MEDIA_PAD_FL_SOURCE; priv->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_COMPOSER; ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pads); if (ret < 0) return ret; strncpy(priv->sd.name, dev_name(cif_dev->dev), sizeof(priv->sd.name)); return 0; } static int rkcif_subdev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct v4l2_subdev *sd; struct sditf_priv *priv; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; sd = &priv->sd; v4l2_subdev_init(sd, &sditf_subdev_ops); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; snprintf(sd->name, sizeof(sd->name), "rockchip-cif-sditf"); sd->dev = dev; platform_set_drvdata(pdev, &sd->entity); rkcif_sditf_attach_cifdev(priv); ret = rkcif_subdev_media_init(priv); if (ret < 0) return ret; pm_runtime_enable(&pdev->dev); return 0; } static int rkcif_subdev_remove(struct platform_device *pdev) { struct media_entity *me = platform_get_drvdata(pdev); struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(me); media_entity_cleanup(&sd->entity); pm_runtime_disable(&pdev->dev); return 0; } static int sditf_runtime_suspend(struct device *dev) { return 0; } static int sditf_runtime_resume(struct device *dev) { return 0; } static const struct dev_pm_ops rkcif_subdev_pm_ops = { SET_RUNTIME_PM_OPS(sditf_runtime_suspend, sditf_runtime_resume, NULL) }; static const struct of_device_id rkcif_subdev_match_id[] = { { .compatible = "rockchip,rkcif-sditf", }, {} }; MODULE_DEVICE_TABLE(of, rkcif_subdev_match_id); struct platform_driver rkcif_subdev_driver = { .probe = rkcif_subdev_probe, .remove = rkcif_subdev_remove, .driver = { .name = "rkcif_sditf", .pm = &rkcif_subdev_pm_ops, .of_match_table = rkcif_subdev_match_id, }, }; EXPORT_SYMBOL(rkcif_subdev_driver); MODULE_AUTHOR("Rockchip Camera/ISP team"); MODULE_DESCRIPTION("Rockchip CIF platform driver"); MODULE_LICENSE("GPL v2");