From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935752AbeCHJth (ORCPT ); Thu, 8 Mar 2018 04:49:37 -0500 Received: from mail-pg0-f65.google.com ([74.125.83.65]:32930 "EHLO mail-pg0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935657AbeCHJt2 (ORCPT ); Thu, 8 Mar 2018 04:49:28 -0500 X-Google-Smtp-Source: AG47ELvuYXHxzaLfDFUwcgQ5L6ziprq46m0zSXQ3qtRdric+iMD28FTds8jML1SfRbLxVEnPcKJXXw== From: Jacob Chen To: linux-rockchip@lists.infradead.org Cc: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, mchehab@kernel.org, linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, hans.verkuil@cisco.com, tfiga@chromium.org, zhengsq@rock-chips.com, laurent.pinchart@ideasonboard.com, zyc@rock-chips.com, eddie.cai.linux@gmail.com, jeffy.chen@rock-chips.com, devicetree@vger.kernel.org, heiko@sntech.de, Jacob Chen , Jacob Chen , Allon Huang Subject: [PATCH v6 09/17] media: rkisp1: add rockchip isp1 core driver Date: Thu, 8 Mar 2018 17:47:59 +0800 Message-Id: <20180308094807.9443-10-jacob-chen@iotwrt.com> X-Mailer: git-send-email 2.16.1 In-Reply-To: <20180308094807.9443-1-jacob-chen@iotwrt.com> References: <20180308094807.9443-1-jacob-chen@iotwrt.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Jacob Chen Add the core driver for rockchip isp1. Signed-off-by: Jacob Chen Signed-off-by: Shunqian Zheng Signed-off-by: Yichong Zhong Signed-off-by: Jacob Chen Signed-off-by: Eddie Cai Signed-off-by: Jeffy Chen Signed-off-by: Allon Huang Signed-off-by: Tomasz Figa --- drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 1 + drivers/media/platform/rockchip/isp1/Makefile | 8 + drivers/media/platform/rockchip/isp1/common.h | 110 +++++ drivers/media/platform/rockchip/isp1/dev.c | 626 ++++++++++++++++++++++++++ drivers/media/platform/rockchip/isp1/dev.h | 93 ++++ 6 files changed, 848 insertions(+) create mode 100644 drivers/media/platform/rockchip/isp1/Makefile create mode 100644 drivers/media/platform/rockchip/isp1/common.h create mode 100644 drivers/media/platform/rockchip/isp1/dev.c create mode 100644 drivers/media/platform/rockchip/isp1/dev.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 614fbef08ddc..3dff763b731d 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -117,6 +117,16 @@ config VIDEO_QCOM_CAMSS select VIDEOBUF2_DMA_SG select V4L2_FWNODE +config VIDEO_ROCKCHIP_ISP1 + tristate "Rockchip Image Signal Processing v1 Unit driver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on ARCH_ROCKCHIP || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + default n + ---help--- + Support for ISP1 on the rockchip SoC. + config VIDEO_S3C_CAMIF tristate "Samsung S3C24XX/S3C64XX SoC Camera Interface driver" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 7f3080437be6..0d5e1b3e6f22 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rockchip/isp1/ obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/ obj-y += omap/ diff --git a/drivers/media/platform/rockchip/isp1/Makefile b/drivers/media/platform/rockchip/isp1/Makefile new file mode 100644 index 000000000000..18af64853734 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += video_rkisp1.o +video_rkisp1-objs += rkisp1.o \ + dev.o \ + regs.o \ + isp_stats.o \ + isp_params.o \ + mipi_dphy_sy.o \ + capture.o diff --git a/drivers/media/platform/rockchip/isp1/common.h b/drivers/media/platform/rockchip/isp1/common.h new file mode 100644 index 000000000000..b58cdaf7da06 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/common.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_COMMON_H +#define _RKISP1_COMMON_H + +#include +#include +#include +#include +#include +#include + +#define RKISP1_DEFAULT_WIDTH 800 +#define RKISP1_DEFAULT_HEIGHT 600 + +#define RKISP1_MAX_STREAM 2 +#define RKISP1_STREAM_SP 0 +#define RKISP1_STREAM_MP 1 + +#define RKISP1_PLANE_Y 0 +#define RKISP1_PLANE_CB 1 +#define RKISP1_PLANE_CR 2 + +enum rkisp1_sd_type { + RKISP1_SD_SENSOR, + RKISP1_SD_PHY_CSI, + RKISP1_SD_VCM, + RKISP1_SD_FLASH, + RKISP1_SD_MAX, +}; + +/* One structure per video node */ +struct rkisp1_vdev_node { + struct vb2_queue buf_queue; + /* vfd lock */ + struct mutex vlock; + struct video_device vdev; + struct media_pad pad; +}; + +enum rkisp1_fmt_pix_type { + FMT_YUV, + FMT_RGB, + FMT_BAYER, + FMT_JPEG, + FMT_MAX +}; + +enum rkisp1_fmt_raw_pat_type { + RAW_RGGB = 0, + RAW_GRBG, + RAW_GBRG, + RAW_BGGR, +}; + +enum rkisp1_state { + /* path not yet opened: */ + RKISP1_STATE_DISABLED, + /* path opened and configured, ready for streaming: */ + RKISP1_STATE_READY, + /* path is streaming: */ + RKISP1_STATE_STREAMING +}; + +struct rkisp1_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; + union { + u32 buff_addr[VIDEO_MAX_PLANES]; + void *vaddr[VIDEO_MAX_PLANES]; + }; +}; + +struct rkisp1_dummy_buffer { + void *vaddr; + dma_addr_t dma_addr; + u32 size; +}; + +extern int rkisp1_debug; + +static inline +struct rkisp1_vdev_node *vdev_to_node(struct video_device *vdev) +{ + return container_of(vdev, struct rkisp1_vdev_node, vdev); +} + +static inline struct rkisp1_vdev_node *queue_to_node(struct vb2_queue *q) +{ + return container_of(q, struct rkisp1_vdev_node, buf_queue); +} + +static inline struct rkisp1_buffer *to_rkisp1_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct rkisp1_buffer, vb); +} + +static inline struct vb2_queue *to_vb2_queue(struct file *file) +{ + struct rkisp1_vdev_node *vnode = video_drvdata(file); + + return &vnode->buf_queue; +} + +#endif /* _RKISP1_COMMON_H */ diff --git a/drivers/media/platform/rockchip/isp1/dev.c b/drivers/media/platform/rockchip/isp1/dev.c new file mode 100644 index 000000000000..990ac28e4107 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "regs.h" + +struct isp_match_data { + const char * const *clks; + int size; +}; + +int rkisp1_debug; +module_param_named(debug, rkisp1_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/***************************** pipeline operations*******************************/ + +static int __isp_pipeline_prepare(struct rkisp1_pipeline *p, + struct media_entity *me) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + struct v4l2_subdev *sd; + int i; + + p->num_subdevs = 0; + memset(p->subdevs, 0, sizeof(p->subdevs)); + + while (1) { + struct media_pad *pad = NULL; + + /* Find remote source pad */ + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + pad = media_entity_remote_pad(spad); + if (pad) + break; + } + + if (!pad) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + if (sd != &dev->isp_sdev.sd) + p->subdevs[p->num_subdevs++] = sd; + + me = &sd->entity; + if (me->num_pads == 1) + break; + } + return 0; +} + +static int __subdev_set_power(struct v4l2_subdev *sd, int on) +{ + int ret; + + if (!sd) + return -ENXIO; + + ret = v4l2_subdev_call(sd, core, s_power, on); + + return ret != -ENOIOCTLCMD ? ret : 0; +} + +static int __isp_pipeline_s_power(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if (on) { + __subdev_set_power(&dev->isp_sdev.sd, true); + + for (i = p->num_subdevs - 1; i >= 0; --i) { + ret = __subdev_set_power(p->subdevs[i], true); + if (ret < 0 && ret != -ENXIO) + goto err_power_off; + } + } else { + for (i = 0; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + + __subdev_set_power(&dev->isp_sdev.sd, false); + } + + return 0; + +err_power_off: + for (++i; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + __subdev_set_power(&dev->isp_sdev.sd, true); + return ret; +} + +static int rkisp1_pipeline_open(struct rkisp1_pipeline *p, + struct media_entity *me, + bool prepare) +{ + int ret; + + if (WARN_ON(!p || !me)) + return -EINVAL; + if (atomic_inc_return(&p->power_cnt) > 1) + return 0; + + /* go through media graphic and get subdevs */ + if (prepare) + __isp_pipeline_prepare(p, me); + + if (!p->num_subdevs) + return -EINVAL; + + ret = __isp_pipeline_s_power(p, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int rkisp1_pipeline_close(struct rkisp1_pipeline *p) +{ + int ret; + + if (atomic_dec_return(&p->power_cnt) > 0) + return 0; + ret = __isp_pipeline_s_power(p, 0); + + return ret == -ENXIO ? 0 : ret; +} + +/* + * stream-on order: isp_subdev, mipi dphy, sensor + * stream-off order: mipi dphy, sensor, isp_subdev + */ +static int rkisp1_pipeline_set_stream(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if ((on && atomic_inc_return(&p->stream_cnt) > 1) || + (!on && atomic_dec_return(&p->stream_cnt) > 0)) + return 0; + + if (on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, true); + + /* phy -> sensor */ + for (i = 0; i < p->num_subdevs; ++i) { + ret = v4l2_subdev_call(p->subdevs[i], video, s_stream, on); + if (on && ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + goto err_stream_off; + } + + if (!on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + + return 0; + +err_stream_off: + for (--i; i >= 0; --i) + v4l2_subdev_call(p->subdevs[i], video, s_stream, false); + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + return ret; +} + +/***************************** media controller *******************************/ +/* See http://opensource.rock-chips.com/wiki_Rockchip-isp1 for Topology */ + +static int rkisp1_create_links(struct rkisp1_device *dev) +{ + struct media_entity *source, *sink; + unsigned int flags, s, pad; + int ret; + + /* sensor links(or mipi-phy) */ + for (s = 0; s < dev->num_sensors; ++s) { + struct rkisp1_sensor_info *sensor = &dev->sensors[s]; + + for (pad = 0; pad < sensor->sd->entity.num_pads; pad++) + if (sensor->sd->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == sensor->sd->entity.num_pads) { + dev_err(dev->dev, + "failed to find src pad for %s\n", + sensor->sd->name); + + return -ENXIO; + } + + ret = media_create_pad_link( + &sensor->sd->entity, pad, + &dev->isp_sdev.sd.entity, + RKISP1_ISP_PAD_SINK + s, + s ? 0 : MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(dev->dev, + "failed to create link for %s\n", + sensor->sd->name); + return ret; + } + } + + /* params links */ + source = &dev->params_vdev.vnode.vdev.entity; + sink = &dev->isp_sdev.sd.entity; + flags = MEDIA_LNK_FL_ENABLED; + ret = media_create_pad_link(source, 0, sink, + RKISP1_ISP_PAD_SINK_PARAMS, flags); + if (ret < 0) + return ret; + + /* create isp internal links */ + /* SP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_SP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* MP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_MP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* 3A stats links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stats_vdev.vnode.vdev.entity; + return media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_STATS, + sink, 0, flags); +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct rkisp1_device *dev; + int ret; + + dev = container_of(notifier, struct rkisp1_device, notifier); + + mutex_lock(&dev->media_dev.graph_mutex); + ret = rkisp1_create_links(dev); + if (ret < 0) + goto unlock; + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret < 0) + goto unlock; + + v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n"); + +unlock: + mutex_unlock(&dev->media_dev.graph_mutex); + return ret; +} + +struct rkisp1_async_subdev { + struct v4l2_async_subdev asd; + struct v4l2_mbus_config mbus; +}; + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_device *isp_dev = container_of(notifier, + struct rkisp1_device, notifier); + struct rkisp1_async_subdev *s_asd = container_of(asd, + struct rkisp1_async_subdev, asd); + + if (isp_dev->num_sensors == ARRAY_SIZE(isp_dev->sensors)) + return -EBUSY; + + isp_dev->sensors[isp_dev->num_sensors].mbus = s_asd->mbus; + isp_dev->sensors[isp_dev->num_sensors].sd = subdev; + ++isp_dev->num_sensors; + + v4l2_dbg(1, rkisp1_debug, subdev, "Async registered subdev\n"); + + return 0; +} + +static int rkisp1_fwnode_parse(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_async_subdev *rk_asd = + container_of(asd, struct rkisp1_async_subdev, asd); + struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel; + + /* + * MIPI sensor is linked with a mipi dphy and its media bus config can + * not be get in here + */ + if (vep->bus_type != V4L2_MBUS_BT656 && + vep->bus_type != V4L2_MBUS_PARALLEL) + return 0; + + rk_asd->mbus.flags = bus->flags; + rk_asd->mbus.type = vep->bus_type; + + return 0; +} + +static const struct v4l2_async_notifier_operations subdev_notifier_ops = { + .bound = subdev_notifier_bound, + .complete = subdev_notifier_complete, +}; + +static int isp_subdev_notifier(struct rkisp1_device *isp_dev) +{ + struct v4l2_async_notifier *ntf = &isp_dev->notifier; + struct device *dev = isp_dev->dev; + int ret; + + ret = v4l2_async_notifier_parse_fwnode_endpoints( + dev, ntf, sizeof(struct rkisp1_async_subdev), + rkisp1_fwnode_parse); + if (ret < 0) + return ret; + + if (!ntf->num_subdevs) + return -ENODEV; /* no endpoint */ + + ntf->ops = &subdev_notifier_ops; + + return v4l2_async_notifier_register(&isp_dev->v4l2_dev, ntf); +} + +/***************************** platform deive *******************************/ + +static int rkisp1_register_platform_subdevs(struct rkisp1_device *dev) +{ + int ret; + + ret = rkisp1_register_isp_subdev(dev, &dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = rkisp1_register_stream_vdevs(dev); + if (ret < 0) + goto err_unreg_isp_subdev; + + ret = rkisp1_register_stats_vdev(&dev->stats_vdev, &dev->v4l2_dev, dev); + if (ret < 0) + goto err_unreg_stream_vdev; + + ret = rkisp1_register_params_vdev(&dev->params_vdev, &dev->v4l2_dev, + dev); + if (ret < 0) + goto err_unreg_stats_vdev; + + ret = isp_subdev_notifier(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "Failed to register subdev notifier(%d)\n", ret); + goto err_unreg_params_vdev; + } + + return 0; +err_unreg_params_vdev: + rkisp1_unregister_params_vdev(&dev->params_vdev); +err_unreg_stats_vdev: + rkisp1_unregister_stats_vdev(&dev->stats_vdev); +err_unreg_stream_vdev: + rkisp1_unregister_stream_vdevs(dev); +err_unreg_isp_subdev: + rkisp1_unregister_isp_subdev(dev); + return ret; +} + +static const char * const rk3399_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "aclk_isp_wrap", + "hclk_isp_wrap", +}; + +static const char * const rk3288_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "pclk_isp_in", + "sclk_isp_jpe", +}; + +static const struct isp_match_data rk3288_isp_clk_data = { + .clks = rk3288_isp_clks, + .size = ARRAY_SIZE(rk3288_isp_clks), +}; + +static const struct isp_match_data rk3399_isp_clk_data = { + .clks = rk3399_isp_clks, + .size = ARRAY_SIZE(rk3399_isp_clks), +}; + +static const struct of_device_id rkisp1_plat_of_match[] = { + { + .compatible = "rockchip,rk3288-cif-isp", + .data = &rk3288_isp_clk_data, + }, { + .compatible = "rockchip,rk3399-cif-isp", + .data = &rk3399_isp_clk_data, + }, + {}, +}; + +static irqreturn_t rkisp1_irq_handler(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1_dev = dev_get_drvdata(dev); + void __iomem *base = rkisp1_dev->base_addr; + unsigned int mis_val, i; + + mis_val = readl(rkisp1_dev->base_addr + CIF_ISP_MIS); + if (mis_val) + rkisp1_isp_isr(mis_val, rkisp1_dev); + + mis_val = readl(rkisp1_dev->base_addr + CIF_MIPI_MIS); + if (mis_val) + rkisp1_mipi_isr(mis_val, rkisp1_dev); + + for (i = 0; i < RKISP1_MAX_STREAM; ++i) { + struct rkisp1_stream *stream = &rkisp1_dev->stream[i]; + + if (stream->ops->is_frame_end_int_masked(base)) + rkisp1_mi_isr(stream); + } + + return IRQ_HANDLED; +} + +static void rkisp1_disable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i; + + for (i = rkisp1_dev->clk_size - 1; i >= 0; i--) + clk_disable_unprepare(rkisp1_dev->clks[i]); +} + +static int rkisp1_enable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i, ret = -EINVAL; + + for (i = 0; i < rkisp1_dev->clk_size; i++) { + ret = clk_prepare_enable(rkisp1_dev->clks[i]); + if (ret < 0) + goto err; + } + return 0; +err: + for (--i; i >= 0; --i) + clk_disable_unprepare(rkisp1_dev->clks[i]); + return ret; +} + +static int rkisp1_plat_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev; + struct rkisp1_device *isp_dev; + const struct isp_match_data *clk_data; + + struct resource *res; + int i, ret, irq; + + match = of_match_node(rkisp1_plat_of_match, node); + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + dev_set_drvdata(dev, isp_dev); + isp_dev->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isp_dev->base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(isp_dev->base_addr)) + return PTR_ERR(isp_dev->base_addr); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rkisp1_irq_handler, IRQF_SHARED, + dev_driver_string(dev), dev); + if (ret < 0) { + dev_err(dev, "request irq failed: %d\n", ret); + return ret; + } + + isp_dev->irq = irq; + clk_data = match->data; + for (i = 0; i < clk_data->size; i++) { + struct clk *clk = devm_clk_get(dev, clk_data->clks[i]); + + if (IS_ERR(clk)) { + dev_err(dev, "failed to get %s\n", clk_data->clks[i]); + return PTR_ERR(clk); + } + isp_dev->clks[i] = clk; + } + isp_dev->clk_size = clk_data->size; + + atomic_set(&isp_dev->pipe.power_cnt, 0); + atomic_set(&isp_dev->pipe.stream_cnt, 0); + isp_dev->pipe.open = rkisp1_pipeline_open; + isp_dev->pipe.close = rkisp1_pipeline_close; + isp_dev->pipe.set_stream = rkisp1_pipeline_set_stream; + + rkisp1_stream_init(isp_dev, RKISP1_STREAM_SP); + rkisp1_stream_init(isp_dev, RKISP1_STREAM_MP); + + strlcpy(isp_dev->media_dev.model, "rkisp1", + sizeof(isp_dev->media_dev.model)); + isp_dev->media_dev.dev = &pdev->dev; + media_device_init(&isp_dev->media_dev); + + v4l2_dev = &isp_dev->v4l2_dev; + v4l2_dev->mdev = &isp_dev->media_dev; + strlcpy(v4l2_dev->name, "rkisp1", sizeof(v4l2_dev->name)); + v4l2_ctrl_handler_init(&isp_dev->ctrl_handler, 5); + v4l2_dev->ctrl_handler = &isp_dev->ctrl_handler; + + ret = v4l2_device_register(isp_dev->dev, &isp_dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = media_device_register(&isp_dev->media_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to register media device: %d\n", + ret); + goto err_unreg_v4l2_dev; + } + + /* create & register platefom subdev (from of_node) */ + ret = rkisp1_register_platform_subdevs(isp_dev); + if (ret < 0) + goto err_unreg_media_dev; + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_unreg_media_dev: + media_device_unregister(&isp_dev->media_dev); +err_unreg_v4l2_dev: + v4l2_device_unregister(&isp_dev->v4l2_dev); + return ret; +} + +static int rkisp1_plat_remove(struct platform_device *pdev) +{ + struct rkisp1_device *isp_dev = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + media_device_unregister(&isp_dev->media_dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); + rkisp1_unregister_params_vdev(&isp_dev->params_vdev); + rkisp1_unregister_stats_vdev(&isp_dev->stats_vdev); + rkisp1_unregister_stream_vdevs(isp_dev); + rkisp1_unregister_isp_subdev(isp_dev); + + return 0; +} + +static int __maybe_unused rkisp1_runtime_suspend(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + + rkisp1_disable_sys_clk(isp_dev); + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused rkisp1_runtime_resume(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret < 0) + return ret; + rkisp1_enable_sys_clk(isp_dev); + + return 0; +} + +static const struct dev_pm_ops rkisp1_plat_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL) +}; + +static struct platform_driver rkisp1_plat_drv = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(rkisp1_plat_of_match), + .pm = &rkisp1_plat_pm_ops, + }, + .probe = rkisp1_plat_probe, + .remove = rkisp1_plat_remove, +}; + +module_platform_driver(rkisp1_plat_drv); +MODULE_AUTHOR("Rockchip Camera/ISP team"); +MODULE_DESCRIPTION("Rockchip ISP1 platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/media/platform/rockchip/isp1/dev.h b/drivers/media/platform/rockchip/isp1/dev.h new file mode 100644 index 000000000000..bdedbb5e5550 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_DEV_H +#define _RKISP1_DEV_H + +#include "capture.h" +#include "rkisp1.h" +#include "isp_params.h" +#include "isp_stats.h" + +#define DRIVER_NAME "rkisp1" +#define ISP_VDEV_NAME DRIVER_NAME "_ispdev" +#define SP_VDEV_NAME DRIVER_NAME "_selfpath" +#define MP_VDEV_NAME DRIVER_NAME "_mainpath" +#define DMA_VDEV_NAME DRIVER_NAME "_dmapath" + +#define GRP_ID_SENSOR BIT(0) +#define GRP_ID_MIPIPHY BIT(1) +#define GRP_ID_ISP BIT(2) +#define GRP_ID_ISP_MP BIT(3) +#define GRP_ID_ISP_SP BIT(4) + +#define RKISP1_MAX_BUS_CLK 8 +#define RKISP1_MAX_SENSOR 2 +#define RKISP1_MAX_PIPELINE 4 + +/* + * struct rkisp1_pipeline - An ISP hardware pipeline + * + * Capture device call other devices via pipeline + * + * @num_subdevs: number of linked subdevs + * @power_cnt: pipeline power count + * @stream_cnt: stream power count + */ +struct rkisp1_pipeline { + struct media_pipeline pipe; + int num_subdevs; + atomic_t power_cnt; + atomic_t stream_cnt; + struct v4l2_subdev *subdevs[RKISP1_MAX_PIPELINE]; + int (*open)(struct rkisp1_pipeline *p, + struct media_entity *me, bool prepare); + int (*close)(struct rkisp1_pipeline *p); + int (*set_stream)(struct rkisp1_pipeline *p, bool on); +}; + +/* + * struct rkisp1_sensor_info - Sensor information + * @mbus: media bus configuration + */ +struct rkisp1_sensor_info { + struct v4l2_subdev *sd; + struct v4l2_mbus_config mbus; +}; + +/* + * struct rkisp1_device - ISP platform device + * @base_addr: base register address + * @active_sensor: sensor in-use, set when streaming on + * @isp_sdev: ISP sub-device + * @rkisp1_stream: capture video device + * @stats_vdev: ISP statistics output device + * @params_vdev: ISP input parameters device + */ +struct rkisp1_device { + void __iomem *base_addr; + int irq; + struct device *dev; + struct clk *clks[RKISP1_MAX_BUS_CLK]; + int clk_size; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + struct media_device media_dev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *subdevs[RKISP1_SD_MAX]; + struct rkisp1_sensor_info *active_sensor; + struct rkisp1_sensor_info sensors[RKISP1_MAX_SENSOR]; + int num_sensors; + struct rkisp1_isp_subdev isp_sdev; + struct rkisp1_stream stream[RKISP1_MAX_STREAM]; + struct rkisp1_isp_stats_vdev stats_vdev; + struct rkisp1_isp_params_vdev params_vdev; + struct rkisp1_pipeline pipe; + struct vb2_alloc_ctx *alloc_ctx; +}; + +#endif -- 2.16.1 From mboxrd@z Thu Jan 1 00:00:00 1970 From: Jacob Chen Subject: [PATCH v6 09/17] media: rkisp1: add rockchip isp1 core driver Date: Thu, 8 Mar 2018 17:47:59 +0800 Message-ID: <20180308094807.9443-10-jacob-chen@iotwrt.com> References: <20180308094807.9443-1-jacob-chen@iotwrt.com> Mime-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Return-path: In-Reply-To: <20180308094807.9443-1-jacob-chen@iotwrt.com> List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=m.gmane.org@lists.infradead.org To: linux-rockchip@lists.infradead.org Cc: devicetree@vger.kernel.org, eddie.cai.linux@gmail.com, Jacob Chen , heiko@sntech.de, Jacob Chen , jeffy.chen@rock-chips.com, zyc@rock-chips.com, linux-kernel@vger.kernel.org, tfiga@chromium.org, hans.verkuil@cisco.com, laurent.pinchart@ideasonboard.com, sakari.ailus@linux.intel.com, Allon Huang , mchehab@kernel.org, zhengsq@rock-chips.com, linux-arm-kernel@lists.infradead.org, linux-media@vger.kernel.org List-Id: devicetree@vger.kernel.org From: Jacob Chen Add the core driver for rockchip isp1. Signed-off-by: Jacob Chen Signed-off-by: Shunqian Zheng Signed-off-by: Yichong Zhong Signed-off-by: Jacob Chen Signed-off-by: Eddie Cai Signed-off-by: Jeffy Chen Signed-off-by: Allon Huang Signed-off-by: Tomasz Figa --- drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 1 + drivers/media/platform/rockchip/isp1/Makefile | 8 + drivers/media/platform/rockchip/isp1/common.h | 110 +++++ drivers/media/platform/rockchip/isp1/dev.c | 626 ++++++++++++++++++++++++++ drivers/media/platform/rockchip/isp1/dev.h | 93 ++++ 6 files changed, 848 insertions(+) create mode 100644 drivers/media/platform/rockchip/isp1/Makefile create mode 100644 drivers/media/platform/rockchip/isp1/common.h create mode 100644 drivers/media/platform/rockchip/isp1/dev.c create mode 100644 drivers/media/platform/rockchip/isp1/dev.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 614fbef08ddc..3dff763b731d 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -117,6 +117,16 @@ config VIDEO_QCOM_CAMSS select VIDEOBUF2_DMA_SG select V4L2_FWNODE +config VIDEO_ROCKCHIP_ISP1 + tristate "Rockchip Image Signal Processing v1 Unit driver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on ARCH_ROCKCHIP || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + default n + ---help--- + Support for ISP1 on the rockchip SoC. + config VIDEO_S3C_CAMIF tristate "Samsung S3C24XX/S3C64XX SoC Camera Interface driver" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 7f3080437be6..0d5e1b3e6f22 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rockchip/isp1/ obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/ obj-y += omap/ diff --git a/drivers/media/platform/rockchip/isp1/Makefile b/drivers/media/platform/rockchip/isp1/Makefile new file mode 100644 index 000000000000..18af64853734 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += video_rkisp1.o +video_rkisp1-objs += rkisp1.o \ + dev.o \ + regs.o \ + isp_stats.o \ + isp_params.o \ + mipi_dphy_sy.o \ + capture.o diff --git a/drivers/media/platform/rockchip/isp1/common.h b/drivers/media/platform/rockchip/isp1/common.h new file mode 100644 index 000000000000..b58cdaf7da06 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/common.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_COMMON_H +#define _RKISP1_COMMON_H + +#include +#include +#include +#include +#include +#include + +#define RKISP1_DEFAULT_WIDTH 800 +#define RKISP1_DEFAULT_HEIGHT 600 + +#define RKISP1_MAX_STREAM 2 +#define RKISP1_STREAM_SP 0 +#define RKISP1_STREAM_MP 1 + +#define RKISP1_PLANE_Y 0 +#define RKISP1_PLANE_CB 1 +#define RKISP1_PLANE_CR 2 + +enum rkisp1_sd_type { + RKISP1_SD_SENSOR, + RKISP1_SD_PHY_CSI, + RKISP1_SD_VCM, + RKISP1_SD_FLASH, + RKISP1_SD_MAX, +}; + +/* One structure per video node */ +struct rkisp1_vdev_node { + struct vb2_queue buf_queue; + /* vfd lock */ + struct mutex vlock; + struct video_device vdev; + struct media_pad pad; +}; + +enum rkisp1_fmt_pix_type { + FMT_YUV, + FMT_RGB, + FMT_BAYER, + FMT_JPEG, + FMT_MAX +}; + +enum rkisp1_fmt_raw_pat_type { + RAW_RGGB = 0, + RAW_GRBG, + RAW_GBRG, + RAW_BGGR, +}; + +enum rkisp1_state { + /* path not yet opened: */ + RKISP1_STATE_DISABLED, + /* path opened and configured, ready for streaming: */ + RKISP1_STATE_READY, + /* path is streaming: */ + RKISP1_STATE_STREAMING +}; + +struct rkisp1_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; + union { + u32 buff_addr[VIDEO_MAX_PLANES]; + void *vaddr[VIDEO_MAX_PLANES]; + }; +}; + +struct rkisp1_dummy_buffer { + void *vaddr; + dma_addr_t dma_addr; + u32 size; +}; + +extern int rkisp1_debug; + +static inline +struct rkisp1_vdev_node *vdev_to_node(struct video_device *vdev) +{ + return container_of(vdev, struct rkisp1_vdev_node, vdev); +} + +static inline struct rkisp1_vdev_node *queue_to_node(struct vb2_queue *q) +{ + return container_of(q, struct rkisp1_vdev_node, buf_queue); +} + +static inline struct rkisp1_buffer *to_rkisp1_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct rkisp1_buffer, vb); +} + +static inline struct vb2_queue *to_vb2_queue(struct file *file) +{ + struct rkisp1_vdev_node *vnode = video_drvdata(file); + + return &vnode->buf_queue; +} + +#endif /* _RKISP1_COMMON_H */ diff --git a/drivers/media/platform/rockchip/isp1/dev.c b/drivers/media/platform/rockchip/isp1/dev.c new file mode 100644 index 000000000000..990ac28e4107 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "regs.h" + +struct isp_match_data { + const char * const *clks; + int size; +}; + +int rkisp1_debug; +module_param_named(debug, rkisp1_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/***************************** pipeline operations*******************************/ + +static int __isp_pipeline_prepare(struct rkisp1_pipeline *p, + struct media_entity *me) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + struct v4l2_subdev *sd; + int i; + + p->num_subdevs = 0; + memset(p->subdevs, 0, sizeof(p->subdevs)); + + while (1) { + struct media_pad *pad = NULL; + + /* Find remote source pad */ + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + pad = media_entity_remote_pad(spad); + if (pad) + break; + } + + if (!pad) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + if (sd != &dev->isp_sdev.sd) + p->subdevs[p->num_subdevs++] = sd; + + me = &sd->entity; + if (me->num_pads == 1) + break; + } + return 0; +} + +static int __subdev_set_power(struct v4l2_subdev *sd, int on) +{ + int ret; + + if (!sd) + return -ENXIO; + + ret = v4l2_subdev_call(sd, core, s_power, on); + + return ret != -ENOIOCTLCMD ? ret : 0; +} + +static int __isp_pipeline_s_power(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if (on) { + __subdev_set_power(&dev->isp_sdev.sd, true); + + for (i = p->num_subdevs - 1; i >= 0; --i) { + ret = __subdev_set_power(p->subdevs[i], true); + if (ret < 0 && ret != -ENXIO) + goto err_power_off; + } + } else { + for (i = 0; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + + __subdev_set_power(&dev->isp_sdev.sd, false); + } + + return 0; + +err_power_off: + for (++i; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + __subdev_set_power(&dev->isp_sdev.sd, true); + return ret; +} + +static int rkisp1_pipeline_open(struct rkisp1_pipeline *p, + struct media_entity *me, + bool prepare) +{ + int ret; + + if (WARN_ON(!p || !me)) + return -EINVAL; + if (atomic_inc_return(&p->power_cnt) > 1) + return 0; + + /* go through media graphic and get subdevs */ + if (prepare) + __isp_pipeline_prepare(p, me); + + if (!p->num_subdevs) + return -EINVAL; + + ret = __isp_pipeline_s_power(p, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int rkisp1_pipeline_close(struct rkisp1_pipeline *p) +{ + int ret; + + if (atomic_dec_return(&p->power_cnt) > 0) + return 0; + ret = __isp_pipeline_s_power(p, 0); + + return ret == -ENXIO ? 0 : ret; +} + +/* + * stream-on order: isp_subdev, mipi dphy, sensor + * stream-off order: mipi dphy, sensor, isp_subdev + */ +static int rkisp1_pipeline_set_stream(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if ((on && atomic_inc_return(&p->stream_cnt) > 1) || + (!on && atomic_dec_return(&p->stream_cnt) > 0)) + return 0; + + if (on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, true); + + /* phy -> sensor */ + for (i = 0; i < p->num_subdevs; ++i) { + ret = v4l2_subdev_call(p->subdevs[i], video, s_stream, on); + if (on && ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + goto err_stream_off; + } + + if (!on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + + return 0; + +err_stream_off: + for (--i; i >= 0; --i) + v4l2_subdev_call(p->subdevs[i], video, s_stream, false); + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + return ret; +} + +/***************************** media controller *******************************/ +/* See http://opensource.rock-chips.com/wiki_Rockchip-isp1 for Topology */ + +static int rkisp1_create_links(struct rkisp1_device *dev) +{ + struct media_entity *source, *sink; + unsigned int flags, s, pad; + int ret; + + /* sensor links(or mipi-phy) */ + for (s = 0; s < dev->num_sensors; ++s) { + struct rkisp1_sensor_info *sensor = &dev->sensors[s]; + + for (pad = 0; pad < sensor->sd->entity.num_pads; pad++) + if (sensor->sd->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == sensor->sd->entity.num_pads) { + dev_err(dev->dev, + "failed to find src pad for %s\n", + sensor->sd->name); + + return -ENXIO; + } + + ret = media_create_pad_link( + &sensor->sd->entity, pad, + &dev->isp_sdev.sd.entity, + RKISP1_ISP_PAD_SINK + s, + s ? 0 : MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(dev->dev, + "failed to create link for %s\n", + sensor->sd->name); + return ret; + } + } + + /* params links */ + source = &dev->params_vdev.vnode.vdev.entity; + sink = &dev->isp_sdev.sd.entity; + flags = MEDIA_LNK_FL_ENABLED; + ret = media_create_pad_link(source, 0, sink, + RKISP1_ISP_PAD_SINK_PARAMS, flags); + if (ret < 0) + return ret; + + /* create isp internal links */ + /* SP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_SP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* MP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_MP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* 3A stats links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stats_vdev.vnode.vdev.entity; + return media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_STATS, + sink, 0, flags); +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct rkisp1_device *dev; + int ret; + + dev = container_of(notifier, struct rkisp1_device, notifier); + + mutex_lock(&dev->media_dev.graph_mutex); + ret = rkisp1_create_links(dev); + if (ret < 0) + goto unlock; + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret < 0) + goto unlock; + + v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n"); + +unlock: + mutex_unlock(&dev->media_dev.graph_mutex); + return ret; +} + +struct rkisp1_async_subdev { + struct v4l2_async_subdev asd; + struct v4l2_mbus_config mbus; +}; + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_device *isp_dev = container_of(notifier, + struct rkisp1_device, notifier); + struct rkisp1_async_subdev *s_asd = container_of(asd, + struct rkisp1_async_subdev, asd); + + if (isp_dev->num_sensors == ARRAY_SIZE(isp_dev->sensors)) + return -EBUSY; + + isp_dev->sensors[isp_dev->num_sensors].mbus = s_asd->mbus; + isp_dev->sensors[isp_dev->num_sensors].sd = subdev; + ++isp_dev->num_sensors; + + v4l2_dbg(1, rkisp1_debug, subdev, "Async registered subdev\n"); + + return 0; +} + +static int rkisp1_fwnode_parse(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_async_subdev *rk_asd = + container_of(asd, struct rkisp1_async_subdev, asd); + struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel; + + /* + * MIPI sensor is linked with a mipi dphy and its media bus config can + * not be get in here + */ + if (vep->bus_type != V4L2_MBUS_BT656 && + vep->bus_type != V4L2_MBUS_PARALLEL) + return 0; + + rk_asd->mbus.flags = bus->flags; + rk_asd->mbus.type = vep->bus_type; + + return 0; +} + +static const struct v4l2_async_notifier_operations subdev_notifier_ops = { + .bound = subdev_notifier_bound, + .complete = subdev_notifier_complete, +}; + +static int isp_subdev_notifier(struct rkisp1_device *isp_dev) +{ + struct v4l2_async_notifier *ntf = &isp_dev->notifier; + struct device *dev = isp_dev->dev; + int ret; + + ret = v4l2_async_notifier_parse_fwnode_endpoints( + dev, ntf, sizeof(struct rkisp1_async_subdev), + rkisp1_fwnode_parse); + if (ret < 0) + return ret; + + if (!ntf->num_subdevs) + return -ENODEV; /* no endpoint */ + + ntf->ops = &subdev_notifier_ops; + + return v4l2_async_notifier_register(&isp_dev->v4l2_dev, ntf); +} + +/***************************** platform deive *******************************/ + +static int rkisp1_register_platform_subdevs(struct rkisp1_device *dev) +{ + int ret; + + ret = rkisp1_register_isp_subdev(dev, &dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = rkisp1_register_stream_vdevs(dev); + if (ret < 0) + goto err_unreg_isp_subdev; + + ret = rkisp1_register_stats_vdev(&dev->stats_vdev, &dev->v4l2_dev, dev); + if (ret < 0) + goto err_unreg_stream_vdev; + + ret = rkisp1_register_params_vdev(&dev->params_vdev, &dev->v4l2_dev, + dev); + if (ret < 0) + goto err_unreg_stats_vdev; + + ret = isp_subdev_notifier(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "Failed to register subdev notifier(%d)\n", ret); + goto err_unreg_params_vdev; + } + + return 0; +err_unreg_params_vdev: + rkisp1_unregister_params_vdev(&dev->params_vdev); +err_unreg_stats_vdev: + rkisp1_unregister_stats_vdev(&dev->stats_vdev); +err_unreg_stream_vdev: + rkisp1_unregister_stream_vdevs(dev); +err_unreg_isp_subdev: + rkisp1_unregister_isp_subdev(dev); + return ret; +} + +static const char * const rk3399_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "aclk_isp_wrap", + "hclk_isp_wrap", +}; + +static const char * const rk3288_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "pclk_isp_in", + "sclk_isp_jpe", +}; + +static const struct isp_match_data rk3288_isp_clk_data = { + .clks = rk3288_isp_clks, + .size = ARRAY_SIZE(rk3288_isp_clks), +}; + +static const struct isp_match_data rk3399_isp_clk_data = { + .clks = rk3399_isp_clks, + .size = ARRAY_SIZE(rk3399_isp_clks), +}; + +static const struct of_device_id rkisp1_plat_of_match[] = { + { + .compatible = "rockchip,rk3288-cif-isp", + .data = &rk3288_isp_clk_data, + }, { + .compatible = "rockchip,rk3399-cif-isp", + .data = &rk3399_isp_clk_data, + }, + {}, +}; + +static irqreturn_t rkisp1_irq_handler(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1_dev = dev_get_drvdata(dev); + void __iomem *base = rkisp1_dev->base_addr; + unsigned int mis_val, i; + + mis_val = readl(rkisp1_dev->base_addr + CIF_ISP_MIS); + if (mis_val) + rkisp1_isp_isr(mis_val, rkisp1_dev); + + mis_val = readl(rkisp1_dev->base_addr + CIF_MIPI_MIS); + if (mis_val) + rkisp1_mipi_isr(mis_val, rkisp1_dev); + + for (i = 0; i < RKISP1_MAX_STREAM; ++i) { + struct rkisp1_stream *stream = &rkisp1_dev->stream[i]; + + if (stream->ops->is_frame_end_int_masked(base)) + rkisp1_mi_isr(stream); + } + + return IRQ_HANDLED; +} + +static void rkisp1_disable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i; + + for (i = rkisp1_dev->clk_size - 1; i >= 0; i--) + clk_disable_unprepare(rkisp1_dev->clks[i]); +} + +static int rkisp1_enable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i, ret = -EINVAL; + + for (i = 0; i < rkisp1_dev->clk_size; i++) { + ret = clk_prepare_enable(rkisp1_dev->clks[i]); + if (ret < 0) + goto err; + } + return 0; +err: + for (--i; i >= 0; --i) + clk_disable_unprepare(rkisp1_dev->clks[i]); + return ret; +} + +static int rkisp1_plat_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev; + struct rkisp1_device *isp_dev; + const struct isp_match_data *clk_data; + + struct resource *res; + int i, ret, irq; + + match = of_match_node(rkisp1_plat_of_match, node); + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + dev_set_drvdata(dev, isp_dev); + isp_dev->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isp_dev->base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(isp_dev->base_addr)) + return PTR_ERR(isp_dev->base_addr); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rkisp1_irq_handler, IRQF_SHARED, + dev_driver_string(dev), dev); + if (ret < 0) { + dev_err(dev, "request irq failed: %d\n", ret); + return ret; + } + + isp_dev->irq = irq; + clk_data = match->data; + for (i = 0; i < clk_data->size; i++) { + struct clk *clk = devm_clk_get(dev, clk_data->clks[i]); + + if (IS_ERR(clk)) { + dev_err(dev, "failed to get %s\n", clk_data->clks[i]); + return PTR_ERR(clk); + } + isp_dev->clks[i] = clk; + } + isp_dev->clk_size = clk_data->size; + + atomic_set(&isp_dev->pipe.power_cnt, 0); + atomic_set(&isp_dev->pipe.stream_cnt, 0); + isp_dev->pipe.open = rkisp1_pipeline_open; + isp_dev->pipe.close = rkisp1_pipeline_close; + isp_dev->pipe.set_stream = rkisp1_pipeline_set_stream; + + rkisp1_stream_init(isp_dev, RKISP1_STREAM_SP); + rkisp1_stream_init(isp_dev, RKISP1_STREAM_MP); + + strlcpy(isp_dev->media_dev.model, "rkisp1", + sizeof(isp_dev->media_dev.model)); + isp_dev->media_dev.dev = &pdev->dev; + media_device_init(&isp_dev->media_dev); + + v4l2_dev = &isp_dev->v4l2_dev; + v4l2_dev->mdev = &isp_dev->media_dev; + strlcpy(v4l2_dev->name, "rkisp1", sizeof(v4l2_dev->name)); + v4l2_ctrl_handler_init(&isp_dev->ctrl_handler, 5); + v4l2_dev->ctrl_handler = &isp_dev->ctrl_handler; + + ret = v4l2_device_register(isp_dev->dev, &isp_dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = media_device_register(&isp_dev->media_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to register media device: %d\n", + ret); + goto err_unreg_v4l2_dev; + } + + /* create & register platefom subdev (from of_node) */ + ret = rkisp1_register_platform_subdevs(isp_dev); + if (ret < 0) + goto err_unreg_media_dev; + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_unreg_media_dev: + media_device_unregister(&isp_dev->media_dev); +err_unreg_v4l2_dev: + v4l2_device_unregister(&isp_dev->v4l2_dev); + return ret; +} + +static int rkisp1_plat_remove(struct platform_device *pdev) +{ + struct rkisp1_device *isp_dev = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + media_device_unregister(&isp_dev->media_dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); + rkisp1_unregister_params_vdev(&isp_dev->params_vdev); + rkisp1_unregister_stats_vdev(&isp_dev->stats_vdev); + rkisp1_unregister_stream_vdevs(isp_dev); + rkisp1_unregister_isp_subdev(isp_dev); + + return 0; +} + +static int __maybe_unused rkisp1_runtime_suspend(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + + rkisp1_disable_sys_clk(isp_dev); + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused rkisp1_runtime_resume(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret < 0) + return ret; + rkisp1_enable_sys_clk(isp_dev); + + return 0; +} + +static const struct dev_pm_ops rkisp1_plat_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL) +}; + +static struct platform_driver rkisp1_plat_drv = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(rkisp1_plat_of_match), + .pm = &rkisp1_plat_pm_ops, + }, + .probe = rkisp1_plat_probe, + .remove = rkisp1_plat_remove, +}; + +module_platform_driver(rkisp1_plat_drv); +MODULE_AUTHOR("Rockchip Camera/ISP team"); +MODULE_DESCRIPTION("Rockchip ISP1 platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/media/platform/rockchip/isp1/dev.h b/drivers/media/platform/rockchip/isp1/dev.h new file mode 100644 index 000000000000..bdedbb5e5550 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_DEV_H +#define _RKISP1_DEV_H + +#include "capture.h" +#include "rkisp1.h" +#include "isp_params.h" +#include "isp_stats.h" + +#define DRIVER_NAME "rkisp1" +#define ISP_VDEV_NAME DRIVER_NAME "_ispdev" +#define SP_VDEV_NAME DRIVER_NAME "_selfpath" +#define MP_VDEV_NAME DRIVER_NAME "_mainpath" +#define DMA_VDEV_NAME DRIVER_NAME "_dmapath" + +#define GRP_ID_SENSOR BIT(0) +#define GRP_ID_MIPIPHY BIT(1) +#define GRP_ID_ISP BIT(2) +#define GRP_ID_ISP_MP BIT(3) +#define GRP_ID_ISP_SP BIT(4) + +#define RKISP1_MAX_BUS_CLK 8 +#define RKISP1_MAX_SENSOR 2 +#define RKISP1_MAX_PIPELINE 4 + +/* + * struct rkisp1_pipeline - An ISP hardware pipeline + * + * Capture device call other devices via pipeline + * + * @num_subdevs: number of linked subdevs + * @power_cnt: pipeline power count + * @stream_cnt: stream power count + */ +struct rkisp1_pipeline { + struct media_pipeline pipe; + int num_subdevs; + atomic_t power_cnt; + atomic_t stream_cnt; + struct v4l2_subdev *subdevs[RKISP1_MAX_PIPELINE]; + int (*open)(struct rkisp1_pipeline *p, + struct media_entity *me, bool prepare); + int (*close)(struct rkisp1_pipeline *p); + int (*set_stream)(struct rkisp1_pipeline *p, bool on); +}; + +/* + * struct rkisp1_sensor_info - Sensor information + * @mbus: media bus configuration + */ +struct rkisp1_sensor_info { + struct v4l2_subdev *sd; + struct v4l2_mbus_config mbus; +}; + +/* + * struct rkisp1_device - ISP platform device + * @base_addr: base register address + * @active_sensor: sensor in-use, set when streaming on + * @isp_sdev: ISP sub-device + * @rkisp1_stream: capture video device + * @stats_vdev: ISP statistics output device + * @params_vdev: ISP input parameters device + */ +struct rkisp1_device { + void __iomem *base_addr; + int irq; + struct device *dev; + struct clk *clks[RKISP1_MAX_BUS_CLK]; + int clk_size; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + struct media_device media_dev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *subdevs[RKISP1_SD_MAX]; + struct rkisp1_sensor_info *active_sensor; + struct rkisp1_sensor_info sensors[RKISP1_MAX_SENSOR]; + int num_sensors; + struct rkisp1_isp_subdev isp_sdev; + struct rkisp1_stream stream[RKISP1_MAX_STREAM]; + struct rkisp1_isp_stats_vdev stats_vdev; + struct rkisp1_isp_params_vdev params_vdev; + struct rkisp1_pipeline pipe; + struct vb2_alloc_ctx *alloc_ctx; +}; + +#endif -- 2.16.1 From mboxrd@z Thu Jan 1 00:00:00 1970 From: jacob-chen@iotwrt.com (Jacob Chen) Date: Thu, 8 Mar 2018 17:47:59 +0800 Subject: [PATCH v6 09/17] media: rkisp1: add rockchip isp1 core driver In-Reply-To: <20180308094807.9443-1-jacob-chen@iotwrt.com> References: <20180308094807.9443-1-jacob-chen@iotwrt.com> Message-ID: <20180308094807.9443-10-jacob-chen@iotwrt.com> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org From: Jacob Chen Add the core driver for rockchip isp1. Signed-off-by: Jacob Chen Signed-off-by: Shunqian Zheng Signed-off-by: Yichong Zhong Signed-off-by: Jacob Chen Signed-off-by: Eddie Cai Signed-off-by: Jeffy Chen Signed-off-by: Allon Huang Signed-off-by: Tomasz Figa --- drivers/media/platform/Kconfig | 10 + drivers/media/platform/Makefile | 1 + drivers/media/platform/rockchip/isp1/Makefile | 8 + drivers/media/platform/rockchip/isp1/common.h | 110 +++++ drivers/media/platform/rockchip/isp1/dev.c | 626 ++++++++++++++++++++++++++ drivers/media/platform/rockchip/isp1/dev.h | 93 ++++ 6 files changed, 848 insertions(+) create mode 100644 drivers/media/platform/rockchip/isp1/Makefile create mode 100644 drivers/media/platform/rockchip/isp1/common.h create mode 100644 drivers/media/platform/rockchip/isp1/dev.c create mode 100644 drivers/media/platform/rockchip/isp1/dev.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 614fbef08ddc..3dff763b731d 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -117,6 +117,16 @@ config VIDEO_QCOM_CAMSS select VIDEOBUF2_DMA_SG select V4L2_FWNODE +config VIDEO_ROCKCHIP_ISP1 + tristate "Rockchip Image Signal Processing v1 Unit driver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on ARCH_ROCKCHIP || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + default n + ---help--- + Support for ISP1 on the rockchip SoC. + config VIDEO_S3C_CAMIF tristate "Samsung S3C24XX/S3C64XX SoC Camera Interface driver" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 7f3080437be6..0d5e1b3e6f22 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rockchip/isp1/ obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/ obj-y += omap/ diff --git a/drivers/media/platform/rockchip/isp1/Makefile b/drivers/media/platform/rockchip/isp1/Makefile new file mode 100644 index 000000000000..18af64853734 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += video_rkisp1.o +video_rkisp1-objs += rkisp1.o \ + dev.o \ + regs.o \ + isp_stats.o \ + isp_params.o \ + mipi_dphy_sy.o \ + capture.o diff --git a/drivers/media/platform/rockchip/isp1/common.h b/drivers/media/platform/rockchip/isp1/common.h new file mode 100644 index 000000000000..b58cdaf7da06 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/common.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_COMMON_H +#define _RKISP1_COMMON_H + +#include +#include +#include +#include +#include +#include + +#define RKISP1_DEFAULT_WIDTH 800 +#define RKISP1_DEFAULT_HEIGHT 600 + +#define RKISP1_MAX_STREAM 2 +#define RKISP1_STREAM_SP 0 +#define RKISP1_STREAM_MP 1 + +#define RKISP1_PLANE_Y 0 +#define RKISP1_PLANE_CB 1 +#define RKISP1_PLANE_CR 2 + +enum rkisp1_sd_type { + RKISP1_SD_SENSOR, + RKISP1_SD_PHY_CSI, + RKISP1_SD_VCM, + RKISP1_SD_FLASH, + RKISP1_SD_MAX, +}; + +/* One structure per video node */ +struct rkisp1_vdev_node { + struct vb2_queue buf_queue; + /* vfd lock */ + struct mutex vlock; + struct video_device vdev; + struct media_pad pad; +}; + +enum rkisp1_fmt_pix_type { + FMT_YUV, + FMT_RGB, + FMT_BAYER, + FMT_JPEG, + FMT_MAX +}; + +enum rkisp1_fmt_raw_pat_type { + RAW_RGGB = 0, + RAW_GRBG, + RAW_GBRG, + RAW_BGGR, +}; + +enum rkisp1_state { + /* path not yet opened: */ + RKISP1_STATE_DISABLED, + /* path opened and configured, ready for streaming: */ + RKISP1_STATE_READY, + /* path is streaming: */ + RKISP1_STATE_STREAMING +}; + +struct rkisp1_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; + union { + u32 buff_addr[VIDEO_MAX_PLANES]; + void *vaddr[VIDEO_MAX_PLANES]; + }; +}; + +struct rkisp1_dummy_buffer { + void *vaddr; + dma_addr_t dma_addr; + u32 size; +}; + +extern int rkisp1_debug; + +static inline +struct rkisp1_vdev_node *vdev_to_node(struct video_device *vdev) +{ + return container_of(vdev, struct rkisp1_vdev_node, vdev); +} + +static inline struct rkisp1_vdev_node *queue_to_node(struct vb2_queue *q) +{ + return container_of(q, struct rkisp1_vdev_node, buf_queue); +} + +static inline struct rkisp1_buffer *to_rkisp1_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct rkisp1_buffer, vb); +} + +static inline struct vb2_queue *to_vb2_queue(struct file *file) +{ + struct rkisp1_vdev_node *vnode = video_drvdata(file); + + return &vnode->buf_queue; +} + +#endif /* _RKISP1_COMMON_H */ diff --git a/drivers/media/platform/rockchip/isp1/dev.c b/drivers/media/platform/rockchip/isp1/dev.c new file mode 100644 index 000000000000..990ac28e4107 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "regs.h" + +struct isp_match_data { + const char * const *clks; + int size; +}; + +int rkisp1_debug; +module_param_named(debug, rkisp1_debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/***************************** pipeline operations*******************************/ + +static int __isp_pipeline_prepare(struct rkisp1_pipeline *p, + struct media_entity *me) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + struct v4l2_subdev *sd; + int i; + + p->num_subdevs = 0; + memset(p->subdevs, 0, sizeof(p->subdevs)); + + while (1) { + struct media_pad *pad = NULL; + + /* Find remote source pad */ + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + pad = media_entity_remote_pad(spad); + if (pad) + break; + } + + if (!pad) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + if (sd != &dev->isp_sdev.sd) + p->subdevs[p->num_subdevs++] = sd; + + me = &sd->entity; + if (me->num_pads == 1) + break; + } + return 0; +} + +static int __subdev_set_power(struct v4l2_subdev *sd, int on) +{ + int ret; + + if (!sd) + return -ENXIO; + + ret = v4l2_subdev_call(sd, core, s_power, on); + + return ret != -ENOIOCTLCMD ? ret : 0; +} + +static int __isp_pipeline_s_power(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if (on) { + __subdev_set_power(&dev->isp_sdev.sd, true); + + for (i = p->num_subdevs - 1; i >= 0; --i) { + ret = __subdev_set_power(p->subdevs[i], true); + if (ret < 0 && ret != -ENXIO) + goto err_power_off; + } + } else { + for (i = 0; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + + __subdev_set_power(&dev->isp_sdev.sd, false); + } + + return 0; + +err_power_off: + for (++i; i < p->num_subdevs; ++i) + __subdev_set_power(p->subdevs[i], false); + __subdev_set_power(&dev->isp_sdev.sd, true); + return ret; +} + +static int rkisp1_pipeline_open(struct rkisp1_pipeline *p, + struct media_entity *me, + bool prepare) +{ + int ret; + + if (WARN_ON(!p || !me)) + return -EINVAL; + if (atomic_inc_return(&p->power_cnt) > 1) + return 0; + + /* go through media graphic and get subdevs */ + if (prepare) + __isp_pipeline_prepare(p, me); + + if (!p->num_subdevs) + return -EINVAL; + + ret = __isp_pipeline_s_power(p, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int rkisp1_pipeline_close(struct rkisp1_pipeline *p) +{ + int ret; + + if (atomic_dec_return(&p->power_cnt) > 0) + return 0; + ret = __isp_pipeline_s_power(p, 0); + + return ret == -ENXIO ? 0 : ret; +} + +/* + * stream-on order: isp_subdev, mipi dphy, sensor + * stream-off order: mipi dphy, sensor, isp_subdev + */ +static int rkisp1_pipeline_set_stream(struct rkisp1_pipeline *p, bool on) +{ + struct rkisp1_device *dev = container_of(p, struct rkisp1_device, pipe); + int i, ret; + + if ((on && atomic_inc_return(&p->stream_cnt) > 1) || + (!on && atomic_dec_return(&p->stream_cnt) > 0)) + return 0; + + if (on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, true); + + /* phy -> sensor */ + for (i = 0; i < p->num_subdevs; ++i) { + ret = v4l2_subdev_call(p->subdevs[i], video, s_stream, on); + if (on && ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + goto err_stream_off; + } + + if (!on) + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + + return 0; + +err_stream_off: + for (--i; i >= 0; --i) + v4l2_subdev_call(p->subdevs[i], video, s_stream, false); + v4l2_subdev_call(&dev->isp_sdev.sd, video, s_stream, false); + return ret; +} + +/***************************** media controller *******************************/ +/* See http://opensource.rock-chips.com/wiki_Rockchip-isp1 for Topology */ + +static int rkisp1_create_links(struct rkisp1_device *dev) +{ + struct media_entity *source, *sink; + unsigned int flags, s, pad; + int ret; + + /* sensor links(or mipi-phy) */ + for (s = 0; s < dev->num_sensors; ++s) { + struct rkisp1_sensor_info *sensor = &dev->sensors[s]; + + for (pad = 0; pad < sensor->sd->entity.num_pads; pad++) + if (sensor->sd->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == sensor->sd->entity.num_pads) { + dev_err(dev->dev, + "failed to find src pad for %s\n", + sensor->sd->name); + + return -ENXIO; + } + + ret = media_create_pad_link( + &sensor->sd->entity, pad, + &dev->isp_sdev.sd.entity, + RKISP1_ISP_PAD_SINK + s, + s ? 0 : MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(dev->dev, + "failed to create link for %s\n", + sensor->sd->name); + return ret; + } + } + + /* params links */ + source = &dev->params_vdev.vnode.vdev.entity; + sink = &dev->isp_sdev.sd.entity; + flags = MEDIA_LNK_FL_ENABLED; + ret = media_create_pad_link(source, 0, sink, + RKISP1_ISP_PAD_SINK_PARAMS, flags); + if (ret < 0) + return ret; + + /* create isp internal links */ + /* SP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_SP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* MP links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stream[RKISP1_STREAM_MP].vnode.vdev.entity; + ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_PATH, + sink, 0, flags); + if (ret < 0) + return ret; + + /* 3A stats links */ + source = &dev->isp_sdev.sd.entity; + sink = &dev->stats_vdev.vnode.vdev.entity; + return media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_STATS, + sink, 0, flags); +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct rkisp1_device *dev; + int ret; + + dev = container_of(notifier, struct rkisp1_device, notifier); + + mutex_lock(&dev->media_dev.graph_mutex); + ret = rkisp1_create_links(dev); + if (ret < 0) + goto unlock; + ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev); + if (ret < 0) + goto unlock; + + v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n"); + +unlock: + mutex_unlock(&dev->media_dev.graph_mutex); + return ret; +} + +struct rkisp1_async_subdev { + struct v4l2_async_subdev asd; + struct v4l2_mbus_config mbus; +}; + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_device *isp_dev = container_of(notifier, + struct rkisp1_device, notifier); + struct rkisp1_async_subdev *s_asd = container_of(asd, + struct rkisp1_async_subdev, asd); + + if (isp_dev->num_sensors == ARRAY_SIZE(isp_dev->sensors)) + return -EBUSY; + + isp_dev->sensors[isp_dev->num_sensors].mbus = s_asd->mbus; + isp_dev->sensors[isp_dev->num_sensors].sd = subdev; + ++isp_dev->num_sensors; + + v4l2_dbg(1, rkisp1_debug, subdev, "Async registered subdev\n"); + + return 0; +} + +static int rkisp1_fwnode_parse(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_async_subdev *rk_asd = + container_of(asd, struct rkisp1_async_subdev, asd); + struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel; + + /* + * MIPI sensor is linked with a mipi dphy and its media bus config can + * not be get in here + */ + if (vep->bus_type != V4L2_MBUS_BT656 && + vep->bus_type != V4L2_MBUS_PARALLEL) + return 0; + + rk_asd->mbus.flags = bus->flags; + rk_asd->mbus.type = vep->bus_type; + + return 0; +} + +static const struct v4l2_async_notifier_operations subdev_notifier_ops = { + .bound = subdev_notifier_bound, + .complete = subdev_notifier_complete, +}; + +static int isp_subdev_notifier(struct rkisp1_device *isp_dev) +{ + struct v4l2_async_notifier *ntf = &isp_dev->notifier; + struct device *dev = isp_dev->dev; + int ret; + + ret = v4l2_async_notifier_parse_fwnode_endpoints( + dev, ntf, sizeof(struct rkisp1_async_subdev), + rkisp1_fwnode_parse); + if (ret < 0) + return ret; + + if (!ntf->num_subdevs) + return -ENODEV; /* no endpoint */ + + ntf->ops = &subdev_notifier_ops; + + return v4l2_async_notifier_register(&isp_dev->v4l2_dev, ntf); +} + +/***************************** platform deive *******************************/ + +static int rkisp1_register_platform_subdevs(struct rkisp1_device *dev) +{ + int ret; + + ret = rkisp1_register_isp_subdev(dev, &dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = rkisp1_register_stream_vdevs(dev); + if (ret < 0) + goto err_unreg_isp_subdev; + + ret = rkisp1_register_stats_vdev(&dev->stats_vdev, &dev->v4l2_dev, dev); + if (ret < 0) + goto err_unreg_stream_vdev; + + ret = rkisp1_register_params_vdev(&dev->params_vdev, &dev->v4l2_dev, + dev); + if (ret < 0) + goto err_unreg_stats_vdev; + + ret = isp_subdev_notifier(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "Failed to register subdev notifier(%d)\n", ret); + goto err_unreg_params_vdev; + } + + return 0; +err_unreg_params_vdev: + rkisp1_unregister_params_vdev(&dev->params_vdev); +err_unreg_stats_vdev: + rkisp1_unregister_stats_vdev(&dev->stats_vdev); +err_unreg_stream_vdev: + rkisp1_unregister_stream_vdevs(dev); +err_unreg_isp_subdev: + rkisp1_unregister_isp_subdev(dev); + return ret; +} + +static const char * const rk3399_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "aclk_isp_wrap", + "hclk_isp_wrap", +}; + +static const char * const rk3288_isp_clks[] = { + "clk_isp", + "aclk_isp", + "hclk_isp", + "pclk_isp_in", + "sclk_isp_jpe", +}; + +static const struct isp_match_data rk3288_isp_clk_data = { + .clks = rk3288_isp_clks, + .size = ARRAY_SIZE(rk3288_isp_clks), +}; + +static const struct isp_match_data rk3399_isp_clk_data = { + .clks = rk3399_isp_clks, + .size = ARRAY_SIZE(rk3399_isp_clks), +}; + +static const struct of_device_id rkisp1_plat_of_match[] = { + { + .compatible = "rockchip,rk3288-cif-isp", + .data = &rk3288_isp_clk_data, + }, { + .compatible = "rockchip,rk3399-cif-isp", + .data = &rk3399_isp_clk_data, + }, + {}, +}; + +static irqreturn_t rkisp1_irq_handler(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1_dev = dev_get_drvdata(dev); + void __iomem *base = rkisp1_dev->base_addr; + unsigned int mis_val, i; + + mis_val = readl(rkisp1_dev->base_addr + CIF_ISP_MIS); + if (mis_val) + rkisp1_isp_isr(mis_val, rkisp1_dev); + + mis_val = readl(rkisp1_dev->base_addr + CIF_MIPI_MIS); + if (mis_val) + rkisp1_mipi_isr(mis_val, rkisp1_dev); + + for (i = 0; i < RKISP1_MAX_STREAM; ++i) { + struct rkisp1_stream *stream = &rkisp1_dev->stream[i]; + + if (stream->ops->is_frame_end_int_masked(base)) + rkisp1_mi_isr(stream); + } + + return IRQ_HANDLED; +} + +static void rkisp1_disable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i; + + for (i = rkisp1_dev->clk_size - 1; i >= 0; i--) + clk_disable_unprepare(rkisp1_dev->clks[i]); +} + +static int rkisp1_enable_sys_clk(struct rkisp1_device *rkisp1_dev) +{ + int i, ret = -EINVAL; + + for (i = 0; i < rkisp1_dev->clk_size; i++) { + ret = clk_prepare_enable(rkisp1_dev->clks[i]); + if (ret < 0) + goto err; + } + return 0; +err: + for (--i; i >= 0; --i) + clk_disable_unprepare(rkisp1_dev->clks[i]); + return ret; +} + +static int rkisp1_plat_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev; + struct rkisp1_device *isp_dev; + const struct isp_match_data *clk_data; + + struct resource *res; + int i, ret, irq; + + match = of_match_node(rkisp1_plat_of_match, node); + isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); + if (!isp_dev) + return -ENOMEM; + + dev_set_drvdata(dev, isp_dev); + isp_dev->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isp_dev->base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(isp_dev->base_addr)) + return PTR_ERR(isp_dev->base_addr); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rkisp1_irq_handler, IRQF_SHARED, + dev_driver_string(dev), dev); + if (ret < 0) { + dev_err(dev, "request irq failed: %d\n", ret); + return ret; + } + + isp_dev->irq = irq; + clk_data = match->data; + for (i = 0; i < clk_data->size; i++) { + struct clk *clk = devm_clk_get(dev, clk_data->clks[i]); + + if (IS_ERR(clk)) { + dev_err(dev, "failed to get %s\n", clk_data->clks[i]); + return PTR_ERR(clk); + } + isp_dev->clks[i] = clk; + } + isp_dev->clk_size = clk_data->size; + + atomic_set(&isp_dev->pipe.power_cnt, 0); + atomic_set(&isp_dev->pipe.stream_cnt, 0); + isp_dev->pipe.open = rkisp1_pipeline_open; + isp_dev->pipe.close = rkisp1_pipeline_close; + isp_dev->pipe.set_stream = rkisp1_pipeline_set_stream; + + rkisp1_stream_init(isp_dev, RKISP1_STREAM_SP); + rkisp1_stream_init(isp_dev, RKISP1_STREAM_MP); + + strlcpy(isp_dev->media_dev.model, "rkisp1", + sizeof(isp_dev->media_dev.model)); + isp_dev->media_dev.dev = &pdev->dev; + media_device_init(&isp_dev->media_dev); + + v4l2_dev = &isp_dev->v4l2_dev; + v4l2_dev->mdev = &isp_dev->media_dev; + strlcpy(v4l2_dev->name, "rkisp1", sizeof(v4l2_dev->name)); + v4l2_ctrl_handler_init(&isp_dev->ctrl_handler, 5); + v4l2_dev->ctrl_handler = &isp_dev->ctrl_handler; + + ret = v4l2_device_register(isp_dev->dev, &isp_dev->v4l2_dev); + if (ret < 0) + return ret; + + ret = media_device_register(&isp_dev->media_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to register media device: %d\n", + ret); + goto err_unreg_v4l2_dev; + } + + /* create & register platefom subdev (from of_node) */ + ret = rkisp1_register_platform_subdevs(isp_dev); + if (ret < 0) + goto err_unreg_media_dev; + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_unreg_media_dev: + media_device_unregister(&isp_dev->media_dev); +err_unreg_v4l2_dev: + v4l2_device_unregister(&isp_dev->v4l2_dev); + return ret; +} + +static int rkisp1_plat_remove(struct platform_device *pdev) +{ + struct rkisp1_device *isp_dev = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + media_device_unregister(&isp_dev->media_dev); + v4l2_device_unregister(&isp_dev->v4l2_dev); + rkisp1_unregister_params_vdev(&isp_dev->params_vdev); + rkisp1_unregister_stats_vdev(&isp_dev->stats_vdev); + rkisp1_unregister_stream_vdevs(isp_dev); + rkisp1_unregister_isp_subdev(isp_dev); + + return 0; +} + +static int __maybe_unused rkisp1_runtime_suspend(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + + rkisp1_disable_sys_clk(isp_dev); + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused rkisp1_runtime_resume(struct device *dev) +{ + struct rkisp1_device *isp_dev = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret < 0) + return ret; + rkisp1_enable_sys_clk(isp_dev); + + return 0; +} + +static const struct dev_pm_ops rkisp1_plat_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL) +}; + +static struct platform_driver rkisp1_plat_drv = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(rkisp1_plat_of_match), + .pm = &rkisp1_plat_pm_ops, + }, + .probe = rkisp1_plat_probe, + .remove = rkisp1_plat_remove, +}; + +module_platform_driver(rkisp1_plat_drv); +MODULE_AUTHOR("Rockchip Camera/ISP team"); +MODULE_DESCRIPTION("Rockchip ISP1 platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/media/platform/rockchip/isp1/dev.h b/drivers/media/platform/rockchip/isp1/dev.h new file mode 100644 index 000000000000..bdedbb5e5550 --- /dev/null +++ b/drivers/media/platform/rockchip/isp1/dev.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip isp1 driver + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_DEV_H +#define _RKISP1_DEV_H + +#include "capture.h" +#include "rkisp1.h" +#include "isp_params.h" +#include "isp_stats.h" + +#define DRIVER_NAME "rkisp1" +#define ISP_VDEV_NAME DRIVER_NAME "_ispdev" +#define SP_VDEV_NAME DRIVER_NAME "_selfpath" +#define MP_VDEV_NAME DRIVER_NAME "_mainpath" +#define DMA_VDEV_NAME DRIVER_NAME "_dmapath" + +#define GRP_ID_SENSOR BIT(0) +#define GRP_ID_MIPIPHY BIT(1) +#define GRP_ID_ISP BIT(2) +#define GRP_ID_ISP_MP BIT(3) +#define GRP_ID_ISP_SP BIT(4) + +#define RKISP1_MAX_BUS_CLK 8 +#define RKISP1_MAX_SENSOR 2 +#define RKISP1_MAX_PIPELINE 4 + +/* + * struct rkisp1_pipeline - An ISP hardware pipeline + * + * Capture device call other devices via pipeline + * + * @num_subdevs: number of linked subdevs + * @power_cnt: pipeline power count + * @stream_cnt: stream power count + */ +struct rkisp1_pipeline { + struct media_pipeline pipe; + int num_subdevs; + atomic_t power_cnt; + atomic_t stream_cnt; + struct v4l2_subdev *subdevs[RKISP1_MAX_PIPELINE]; + int (*open)(struct rkisp1_pipeline *p, + struct media_entity *me, bool prepare); + int (*close)(struct rkisp1_pipeline *p); + int (*set_stream)(struct rkisp1_pipeline *p, bool on); +}; + +/* + * struct rkisp1_sensor_info - Sensor information + * @mbus: media bus configuration + */ +struct rkisp1_sensor_info { + struct v4l2_subdev *sd; + struct v4l2_mbus_config mbus; +}; + +/* + * struct rkisp1_device - ISP platform device + * @base_addr: base register address + * @active_sensor: sensor in-use, set when streaming on + * @isp_sdev: ISP sub-device + * @rkisp1_stream: capture video device + * @stats_vdev: ISP statistics output device + * @params_vdev: ISP input parameters device + */ +struct rkisp1_device { + void __iomem *base_addr; + int irq; + struct device *dev; + struct clk *clks[RKISP1_MAX_BUS_CLK]; + int clk_size; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + struct media_device media_dev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *subdevs[RKISP1_SD_MAX]; + struct rkisp1_sensor_info *active_sensor; + struct rkisp1_sensor_info sensors[RKISP1_MAX_SENSOR]; + int num_sensors; + struct rkisp1_isp_subdev isp_sdev; + struct rkisp1_stream stream[RKISP1_MAX_STREAM]; + struct rkisp1_isp_stats_vdev stats_vdev; + struct rkisp1_isp_params_vdev params_vdev; + struct rkisp1_pipeline pipe; + struct vb2_alloc_ctx *alloc_ctx; +}; + +#endif -- 2.16.1