From nobody Sun Apr 19 21:59:15 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6ECCFC433EF for ; Mon, 27 Jun 2022 03:26:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231403AbiF0D0j (ORCPT ); Sun, 26 Jun 2022 23:26:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55344 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231604AbiF0D0e (ORCPT ); Sun, 26 Jun 2022 23:26:34 -0400 Received: from mo-csw.securemx.jp (mo-csw1115.securemx.jp [210.130.202.157]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 16B54273A; Sun, 26 Jun 2022 20:26:17 -0700 (PDT) Received: by mo-csw.securemx.jp (mx-mo-csw1115) id 25R3PWsV019126; Mon, 27 Jun 2022 12:25:33 +0900 X-Iguazu-Qid: 2wGqj56vE7jyjCEks4 X-Iguazu-QSIG: v=2; s=0; t=1656300332; q=2wGqj56vE7jyjCEks4; m=QLscx9iR/REf9kgUk3oV8zUf3Y3/Na9Rch3uCdY6ZSs= Received: from imx12-a.toshiba.co.jp ([38.106.60.135]) by relay.securemx.jp (mx-mr1113) id 25R3PUT7028871 (version=TLSv1.2 cipher=AES128-GCM-SHA256 bits=128 verify=NOT); Mon, 27 Jun 2022 12:25:30 +0900 X-SA-MID: 37652908 From: Yuji Ishikawa To: Hans Verkuil , Laurent Pinchart , Mauro Carvalho Chehab , Nobuhiro Iwamatsu Cc: linux-media@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, yuji2.ishikawa@toshiba.co.jp Subject: media: platform: visconti: Toshiba Visconti Video driver with media control framework. Date: Mon, 27 Jun 2022 12:20:09 +0900 X-TSB-HOP2: ON Message-Id: <20220627032009.10250-1-yuji2.ishikawa@toshiba.co.jp> X-Mailer: git-send-email 2.17.1 In-Reply-To: References: Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Hi, Hans I'm now re-writing the top layer of Visconti5 video input driver following = your suggestions. I just applied media-controller framework, and implemented (limited number = of) compound controlls instead of private ioctls. Please let me know if this implementation satifies the latest standard of m= edia drivers. Here's some description of the driver and the corresponding hardware. Firstly, Visconti5 SoC video capture subsystem is composed of these units. - CSI2RX: receives MIPI CSI-2 signal - L1 ISP: correction and enhancement to RAW picture - L2 ISP: undistortion, scaling, up to 2 ROIs - VDMAC: integrated to L2ISP, transfer picture to main memory. The updated Visconti Video input driver structure is: +--------------+ +----------------+ +----------------+ | image sensor | =3D=3D=3D=3D> | ISP subdevice | =3D=3D=3D=3D> | Capture d= evice | +--------------+ +----------------+ +----------------+ - Image sensor - tested with IMX219 - pad - source - format: SRGGB10 1920x1080 - selection - crop - native - ISP subdevice - corresponds to: CSI2RX, L1ISP, L2ISP - pad - sink - format: the same as sensor::pad::source::format - selection - crop: the same as format - compose: (readonly) intermediate {width, height} derived by undis= tortion and scaling. - compose.bound: (fixed) 8192 x 4096=20 - source - format: YUV8 for RAW/YUV sensor input, RGB888 for RGB sensor input - selection - crop: {left, top, width, height} in isp::pad::sink::selection::co= mpose - compound controls - undistortion and scaling - updates isp::pad::sink::selection::compose - other approx. 30 vendor specific controls to configure ISP operation - Capture device - corresponds to: VDMAC - pad - sink: connected to ISP subdevice In terms of software implementation, the driver roughly composed of two lay= ers. - API layer: to communicate with V4L2 subsystem - viif.c - viif_capture.c: Capture V4L2 device node - viif_isp.c: ISP v4l2 subdevice node - viif_ioctl.c: s_ctrl handlers to configure ISP - HW specific layer: to handle hardware register values - hwd_viif_*.[ch] Along with re-writing, I got some questions. Do you have rules or practices= to resolve them? - How should I define ID number of vendor specific controls, such as V4L2_C= ID_VISCONTI_VIIF_ISP_L2_SET_CROP? It seems, the standard way is to reserve vendor specific IDs relative to = V4L2_CID_USER_BASE. Is that mean, vendor specific CID for ioctl(S_EXT_CTRLs) is shared, limit= ed resources among v4l2 drivers? - How should I explain error/inconsistency of video format, resolution, ISP= configurations among v4l2 (sub-)devices? Because the VIIF HW is not powered when the corresponding /dev/videoX is = closed, settings from media-ctl and v4l2-ctl are held unchecked, therefore, some of inconsistency would be detected at link_validate() cal= l back triggerd by start-streaming. Currently, I set EXECUTE_ON_WRITE flag to every vendor specific controls = and reject changes when the HW is not powered, although I hope there should be better idea. I hope I'm not on the wrong way of re-writing. Regards, Yuji --- Add support to Video Input Interface on Toshiba Visconti ARM SoCs. The Video Input Interface includes CSI2 receiver, frame grabber and image s= ignal processor. Signed-off-by: Yuji Ishikawa --- drivers/media/platform/visconti/Makefile | 1 + drivers/media/platform/visconti/viif.c | 491 +++++++++ .../media/platform/visconti/viif_capture.c | 948 +++++++++++++++++ drivers/media/platform/visconti/viif_ioctl.c | 287 ++++++ drivers/media/platform/visconti/viif_isp.c | 968 ++++++++++++++++++ 5 files changed, 2695 insertions(+) create mode 100644 drivers/media/platform/visconti/viif.c create mode 100644 drivers/media/platform/visconti/viif_capture.c create mode 100644 drivers/media/platform/visconti/viif_ioctl.c create mode 100644 drivers/media/platform/visconti/viif_isp.c diff --git a/drivers/media/platform/visconti/Makefile b/drivers/media/platf= orm/visconti/Makefile index d27da611a..11d80aeb3 100644 --- a/drivers/media/platform/visconti/Makefile +++ b/drivers/media/platform/visconti/Makefile @@ -3,6 +3,7 @@ # Makefile for the Visconti video input device driver # =20 +visconti-viif-objs =3D viif.o viif_capture.o viif_ioctl.o viif_isp.o visconti-viif-objs +=3D hwd_viif_csi2rx.o hwd_viif.o hwd_viif_l1isp.o =20 obj-$(CONFIG_VIDEO_VISCONTI_VIIF) +=3D visconti-viif.o diff --git a/drivers/media/platform/visconti/viif.c b/drivers/media/platfor= m/visconti/viif.c new file mode 100644 index 000000000..ac778d6ab --- /dev/null +++ b/drivers/media/platform/visconti/viif.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* Toshiba Visconti Video Capture Support + * + * (C) Copyright 2022 TOSHIBA CORPORATION + * (C) Copyright 2022 Toshiba Electronic Devices & Storage Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "viif.h" + +#define VIIF_ISP_GUARD_START(viif_dev) = \ + do { = \ + hwd_VIIF_isp_disable_regbuf_auto_transmission(viif_dev->ch); = \ + ndelay(500); = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_ENABLE); = \ + } while (0) + +#define VIIF_ISP_GUARD_END(viif_dev) = \ + do { = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_DISABLE); = \ + hwd_VIIF_isp_set_regbuf_auto_transmission(viif_dev->ch, VIIF_ISP_REGBUF_= 0, \ + VIIF_ISP_REGBUF_0, 0); \ + } while (0) + +void viif_hw_on(struct viif_device *viif_dev) +{ + hwd_VIIF_initialize(viif_dev->ch, viif_dev->csi2host_reg, viif_dev->captu= re_reg); +} + +void viif_hw_off(struct viif_device *viif_dev) +{ + /* Uninitialize HWD driver */ + hwd_VIIF_uninitialize(viif_dev->ch); +} + +static inline struct viif_device *v4l2_to_viif(struct v4l2_device *v4l2_de= v) +{ + return container_of(v4l2_dev, struct viif_device, v4l2_dev); +} + +static struct viif_subdev *to_viif_subdev(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct viif_subdev, asd); +} + +#define VIIF_ERR_M_EVENT_GAMMATBL_SHIFT 8U +#define VIIF_ERR_M_EVENT_GAMMATBL_MASK 0x7U +#define VIIF_SYNC_M_EVENT_DELAY2_SHIFT 2U +#define MAIN_DELAY_INT_ERR_MASK 0x01000000U + +extern void visconti_viif_capture_switch_buffer(struct viif_device *viif_d= ev, uint32_t status_err, + uint32_t l2_transfer_status); + +static void viif_vsync_irq_handler_w_isp(struct viif_device *viif_dev) +{ + uint32_t event_main, event_sub, mask, status_err, l2_transfer_status; + + hwd_VIIF_vsync_irq_handler(viif_dev->ch, &event_main, &event_sub); + + /* Delayed Vsync of MAIN unit */ + if (((event_main >> VIIF_SYNC_M_EVENT_DELAY2_SHIFT) & 0x1U) =3D=3D 0x1U) { + /* unmask timeout error of gamma table */ + mask =3D MAIN_DELAY_INT_ERR_MASK; + hwd_VIIF_main_status_err_set_irq_mask(viif_dev->ch, &mask); + viif_dev->masked_gamma_path =3D 0; + + /* Get abort status of L2ISP */ + VIIF_ISP_GUARD_START(viif_dev); + hwd_VIIF_isp_get_info(viif_dev->ch, VIIF_ISP_REGBUF_0, NULL, NULL, NULL, + &l2_transfer_status, NULL, NULL); + VIIF_ISP_GUARD_END(viif_dev); + + status_err =3D viif_dev->status_err; + viif_dev->status_err =3D 0; + + visconti_viif_capture_switch_buffer(viif_dev, status_err, l2_transfer_st= atus); + } +} + +static void viif_status_err_irq_handler(struct viif_device *viif_dev) +{ + uint32_t event_main, event_sub, val, mask; + + hwd_VIIF_status_err_irq_handler(viif_dev->ch, &event_main, &event_sub); + + if (event_main !=3D 0U) { + /* mask for gamma table time out error which will be unmasked in the nex= t Vsync */ + val =3D (event_main >> VIIF_ERR_M_EVENT_GAMMATBL_SHIFT) & + VIIF_ERR_M_EVENT_GAMMATBL_MASK; + if (val !=3D 0U) { + viif_dev->masked_gamma_path |=3D val; + mask =3D MAIN_DELAY_INT_ERR_MASK | + (viif_dev->masked_gamma_path << VIIF_ERR_M_EVENT_GAMMATBL_SHIFT); + hwd_VIIF_main_status_err_set_irq_mask(viif_dev->ch, &mask); + } + + viif_dev->status_err =3D event_main; + } + dev_err(viif_dev->dev, "Status error 0x%x.\n", event_main); +} + +static void viif_csi2rx_err_irq_handler(struct viif_device *viif_dev) +{ + uint32_t event; + + event =3D hwd_VIIF_csi2rx_err_irq_handler(viif_dev->ch); + dev_err(viif_dev->dev, "CSI2RX error 0x%x.\n", event); +} + +static irqreturn_t visconti_viif_irq(int irq, void *dev_id) +{ + struct viif_device *viif_dev =3D dev_id; + int irq_type =3D irq - viif_dev->irq[0]; + + spin_lock(&viif_dev->lock); + + switch (irq_type) { + case 0: + viif_vsync_irq_handler_w_isp(viif_dev); + break; + case 1: + viif_status_err_irq_handler(viif_dev); + break; + case 2: + viif_csi2rx_err_irq_handler(viif_dev); + break; + } + + spin_unlock(&viif_dev->lock); + + return IRQ_HANDLED; +} + +/* ----- Async Notifier Operations----- */ +static int visconti_viif_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *v4l2_sd, struct v4l2_async_subdev *asd) +{ + struct v4l2_device *v4l2_dev =3D notifier->v4l2_dev; + struct viif_device *viif_dev =3D v4l2_to_viif(v4l2_dev); + struct viif_subdev *viif_sd =3D to_viif_subdev(asd); + + viif_sd->v4l2_sd =3D v4l2_sd; + viif_dev->num_sd++; + + return 0; +} + +static void visconti_viif_create_links(struct viif_device *viif_dev) +{ + unsigned int source_pad; + int ret; + + /* camera subdev pad0 -> isp suddev pad0 */ + ret =3D media_entity_get_fwnode_pad(&viif_dev->sd->v4l2_sd->entity, + viif_dev->sd->v4l2_sd->fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(viif_dev->dev, "failed to find source pad\n"); + return; + } + source_pad =3D ret; + + ret =3D media_create_pad_link(&viif_dev->sd->v4l2_sd->entity, source_pad, + &viif_dev->isp_subdev.sd.entity, VIIF_ISP_PAD_SINK, + MEDIA_LNK_FL_ENABLED); + if (ret) + dev_err(viif_dev->dev, "failed create_pad_link (camera:src -> isp:sink)\= n"); + + ret =3D media_create_pad_link(&viif_dev->isp_subdev.sd.entity, VIIF_ISP_P= AD_SRC, + &viif_dev->vdev.entity, VIIF_CAPTURE_PAD_SINK, + MEDIA_LNK_FL_ENABLED); + if (ret) + dev_err(viif_dev->dev, "failed create_pad_link (isp:src -> camera:sink)\= n"); +} + +static void visconti_viif_notify_unbind(struct v4l2_async_notifier *notifi= er, + struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) +{ + struct v4l2_device *v4l2_dev =3D notifier->v4l2_dev; + struct viif_device *viif_dev =3D v4l2_to_viif(v4l2_dev); + struct viif_subdev *viif_sd =3D to_viif_subdev(asd); + + v4l2_ctrl_handler_free(&viif_dev->ctrl_handler); + v4l2_dev->ctrl_handler =3D NULL; + viif_sd->v4l2_sd =3D NULL; +} + +static int visconti_viif_notify_complete(struct v4l2_async_notifier *notif= ier) +{ + struct v4l2_device *v4l2_dev =3D notifier->v4l2_dev; + struct viif_device *viif_dev =3D v4l2_to_viif(v4l2_dev); + int ret; + + ret =3D v4l2_device_register_subdev_nodes(v4l2_dev); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to register subdev nodes\n"); + return ret; + } + + /* Make sure at least one sensor is primary and use it to initialize */ + if (!viif_dev->sd) { + viif_dev->sd =3D &viif_dev->subdevs[0]; + viif_dev->sd_index =3D 0; + } + + /* TODO: might need to check if subdev's mbus code is valid for this driv= er */ + + ret =3D v4l2_ctrl_add_handler(&viif_dev->ctrl_handler, viif_dev->sd->v4l2= _sd->ctrl_handler, + NULL, true); + if (ret) { + dev_err(v4l2_dev->dev, "Failed to add sensor ctrl_handler"); + return ret; + } + ret =3D v4l2_ctrl_add_handler(&viif_dev->ctrl_handler, &viif_dev->isp_sub= dev.ctrl_handler, + NULL, true); + if (ret) { + dev_err(v4l2_dev->dev, "Failed to add isp subdev ctrl_handler"); + return ret; + } + + visconti_viif_create_links(viif_dev); + + return 0; +} + +static const struct v4l2_async_notifier_operations viif_notify_ops =3D { + .bound =3D visconti_viif_notify_bound, + .unbind =3D visconti_viif_notify_unbind, + .complete =3D visconti_viif_notify_complete, +}; + +/* ----- Probe and Remove ----- */ +static int visconti_viif_init_async_subdevs(struct viif_device *viif_dev, = unsigned int n_sd) +{ + /* Reserve memory for 'n_sd' viif_subdev descriptors. */ + viif_dev->subdevs =3D + devm_kcalloc(viif_dev->dev, n_sd, sizeof(*viif_dev->subdevs), GFP_KERNEL= ); + if (!viif_dev->subdevs) + return -ENOMEM; + + /* Reserve memory for 'n_sd' pointers to async_subdevices. + * viif_dev->asds members will point to &viif_dev.asd + */ + viif_dev->asds =3D devm_kcalloc(viif_dev->dev, n_sd, sizeof(*viif_dev->as= ds), GFP_KERNEL); + if (!viif_dev->asds) + return -ENOMEM; + + viif_dev->sd =3D NULL; + viif_dev->sd_index =3D 0; + viif_dev->num_sd =3D 0; + + return 0; +} + +static int visconti_viif_parse_dt(struct viif_device *viif_dev) +{ + struct device_node *of =3D viif_dev->dev->of_node; + struct v4l2_fwnode_endpoint fw_ep; + struct viif_subdev *viif_sd; + struct device_node *ep; + unsigned int i; + int num_ep; + int ret; + + memset(&fw_ep, 0, sizeof(struct v4l2_fwnode_endpoint)); + + num_ep =3D of_graph_get_endpoint_count(of); + if (!num_ep) + return -ENODEV; + + ret =3D visconti_viif_init_async_subdevs(viif_dev, num_ep); + if (ret) + return ret; + + for (i =3D 0; i < num_ep; i++) { + ep =3D of_graph_get_endpoint_by_regs(of, 0, i); + if (!ep) { + dev_err(viif_dev->dev, "No subdevice connected on endpoint %u.\n", i); + ret =3D -ENODEV; + goto error_put_node; + } + + ret =3D v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fw_ep); + if (ret) { + dev_err(viif_dev->dev, "Unable to parse endpoint #%u.\n", i); + goto error_put_node; + } + + if (fw_ep.bus_type !=3D V4L2_MBUS_CSI2_DPHY || + fw_ep.bus.mipi_csi2.num_data_lanes =3D=3D 0) { + dev_err(viif_dev->dev, "missing CSI-2 properties in endpoint\n"); + ret =3D -EINVAL; + goto error_put_node; + } + + /* Setup the ceu subdevice and the async subdevice. */ + viif_sd =3D &viif_dev->subdevs[i]; + INIT_LIST_HEAD(&viif_sd->asd.list); + + viif_sd->mbus_flags =3D fw_ep.bus.mipi_csi2.flags; + viif_sd->num_lane =3D fw_ep.bus.mipi_csi2.num_data_lanes; + viif_sd->asd.match_type =3D V4L2_ASYNC_MATCH_FWNODE; + viif_sd->asd.match.fwnode =3D + fwnode_graph_get_remote_port_parent(of_fwnode_handle(ep)); + + viif_dev->asds[i] =3D &viif_sd->asd; + of_node_put(ep); + } + + return num_ep; + +error_put_node: + of_node_put(ep); + return ret; +} + +static const struct of_device_id visconti_viif_of_table[] =3D { + { + .compatible =3D "toshiba,visconti-viif", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, visconti_viif_of_table); + +int visconti_viif_isp_register(struct viif_device *viif_dev); +int visconti_viif_capture_register(struct viif_device *viif_dev); +void visconti_viif_isp_unregister(struct viif_device *viif_dev); +void visconti_viif_capture_unregister(struct viif_device *viif_dev); + +static int visconti_viif_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct viif_device *viif_dev; + int ret, i, num_sd; + dma_addr_t table_paddr; + const struct of_device_id *of_id; + + //ret =3D dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + //if (ret) + // return ret; + + viif_dev =3D devm_kzalloc(dev, sizeof(*viif_dev), GFP_KERNEL); + if (!viif_dev) + return -ENOMEM; + + viif_dev->is_powered =3D 0; + + platform_set_drvdata(pdev, viif_dev); + viif_dev->dev =3D dev; + + INIT_LIST_HEAD(&viif_dev->capture); + spin_lock_init(&viif_dev->lock); + mutex_init(&viif_dev->mlock); + + viif_dev->capture_reg =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(viif_dev->capture_reg)) + return PTR_ERR(viif_dev->capture_reg); + + viif_dev->csi2host_reg =3D devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(viif_dev->csi2host_reg)) + return PTR_ERR(viif_dev->csi2host_reg); + + device_property_read_u32(dev, "index", &viif_dev->ch); + + for (i =3D 0; i < 3; i++) { + viif_dev->irq[i] =3D ret =3D platform_get_irq(pdev, i); + if (ret < 0) { + dev_err(dev, "failed to acquire irq resource\n"); + return ret; + } + ret =3D devm_request_irq(dev, viif_dev->irq[i], visconti_viif_irq, 0, "v= iif", + viif_dev); + if (ret) { + dev_err(dev, "irq request failed\n"); + return ret; + } + } + + viif_dev->table_vaddr =3D + dma_alloc_wc(dev, sizeof(struct viif_table_area), &table_paddr, GFP_KERN= EL); + if (!viif_dev->table_vaddr) { + dev_err(dev, "dma_alloc_wc failed\n"); + return -ENOMEM; + } + viif_dev->table_paddr =3D (struct viif_table_area *)table_paddr; + + /* build media_dev */ + viif_dev->media_dev.hw_revision =3D 0; + strscpy(viif_dev->media_dev.model, "visconti_viif", sizeof(viif_dev->medi= a_dev.model)); + viif_dev->media_dev.dev =3D dev; + strscpy(viif_dev->media_dev.bus_info, "platform:visconti_viif", + sizeof(viif_dev->media_dev.bus_info)); + media_device_init(&viif_dev->media_dev); + + /* build v4l2_dev */ + viif_dev->v4l2_dev.mdev =3D &viif_dev->media_dev; + ret =3D v4l2_device_register(dev, &viif_dev->v4l2_dev); + if (ret) + goto error_dma_free; + + ret =3D media_device_register(&viif_dev->media_dev); + if (ret) { + dev_err(dev, "Failed to register media device: %d\n", ret); + goto error_v4l2_unregister; + } + + ret =3D visconti_viif_isp_register(viif_dev); + if (ret) { + dev_err(dev, "failed to register isp sub node: %d\n", ret); + goto error_media_unregister; + } + ret =3D visconti_viif_capture_register(viif_dev); + if (ret) { + dev_err(dev, "failed to register capture node: %d\n", ret); + goto error_media_unregister; + } + ret =3D v4l2_ctrl_handler_init(&viif_dev->ctrl_handler, 20); + if (ret) { + dev_err(dev, "failed on v4l2_ctrl_handler_init"); + return -ENOMEM; + } + viif_dev->v4l2_dev.ctrl_handler =3D &viif_dev->ctrl_handler; + viif_dev->vdev.ctrl_handler =3D &viif_dev->ctrl_handler; + + /* check device type */ + of_id =3D of_match_device(visconti_viif_of_table, dev); + + num_sd =3D visconti_viif_parse_dt(viif_dev); + if (ret < 0) { + ret =3D num_sd; + goto error_media_unregister; + } + + viif_dev->notifier.v4l2_dev =3D &viif_dev->v4l2_dev; + v4l2_async_nf_init(&viif_dev->notifier); + for (i =3D 0; i < num_sd; i++) { + __v4l2_async_nf_add_subdev(&viif_dev->notifier, viif_dev->asds[i]); + } + viif_dev->notifier.ops =3D &viif_notify_ops; + ret =3D v4l2_async_nf_register(&viif_dev->v4l2_dev, &viif_dev->notifier); + if (ret) + goto error_media_unregister; + + return 0; + +error_media_unregister: + media_device_unregister(&viif_dev->media_dev); +error_v4l2_unregister: + v4l2_device_unregister(&viif_dev->v4l2_dev); +error_dma_free: + dma_free_wc(&pdev->dev, sizeof(struct viif_table_area), viif_dev->table_v= addr, + (dma_addr_t)viif_dev->table_paddr); + return ret; +} + +static int visconti_viif_remove(struct platform_device *pdev) +{ + struct viif_device *viif_dev =3D platform_get_drvdata(pdev); + + visconti_viif_isp_unregister(viif_dev); + visconti_viif_capture_unregister(viif_dev); + v4l2_async_nf_unregister(&viif_dev->notifier); + media_device_unregister(&viif_dev->media_dev); + v4l2_device_unregister(&viif_dev->v4l2_dev); + dma_free_wc(&pdev->dev, sizeof(struct viif_table_area), viif_dev->table_v= addr, + (dma_addr_t)viif_dev->table_paddr); + + return 0; +} + +static struct platform_driver visconti_viif_driver =3D { + .probe =3D visconti_viif_probe, + .remove =3D visconti_viif_remove, + .driver =3D { + .name =3D "visconti_viif", + .of_match_table =3D visconti_viif_of_table, + }, +}; + +module_platform_driver(visconti_viif_driver); + +MODULE_AUTHOR("Yuji Ishikawa "); +MODULE_DESCRIPTION("Toshiba Visconti Video Input driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/media/platform/visconti/viif_capture.c b/drivers/media= /platform/visconti/viif_capture.c new file mode 100644 index 000000000..8b0a63852 --- /dev/null +++ b/drivers/media/platform/visconti/viif_capture.c @@ -0,0 +1,948 @@ +#include +#include +#include + +#include "viif.h" + +#define VIIF_CROP_MAX_X_ISP (8062U) +#define VIIF_CROP_MAX_Y_ISP (3966U) +#define VIIF_CROP_MIN_W (128U) +#define VIIF_CROP_MAX_W_ISP (8190U) +#define VIIF_CROP_MIN_H (128U) +#define VIIF_CROP_MAX_H_ISP (4094U) + +#define VIIF_ISP_GUARD_START(viif_dev) = \ + do { = \ + hwd_VIIF_isp_disable_regbuf_auto_transmission(viif_dev->ch); = \ + ndelay(500); = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_ENABLE); = \ + } while (0) + +#define VIIF_ISP_GUARD_END(viif_dev) = \ + do { = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_DISABLE); = \ + hwd_VIIF_isp_set_regbuf_auto_transmission(viif_dev->ch, VIIF_ISP_REGBUF_= 0, \ + VIIF_ISP_REGBUF_0, 0); \ + } while (0) + +struct viif_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; +}; + +static inline struct viif_buffer *vb2_to_viif(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct viif_buffer, vb); +} + +/* ----- ISRs and VB2 Operations ----- */ +static int viif_set_img(struct viif_device *viif_dev, struct vb2_buffer *v= b) +{ + struct v4l2_pix_format_mplane *pix =3D &viif_dev->v4l2_pix; + struct hwd_viif_img next_out_img; + dma_addr_t phys_addr; + int i, ret =3D 0; + + next_out_img.width =3D pix->width; + next_out_img.height =3D pix->height; + next_out_img.format =3D viif_dev->out_format; + + for (i =3D 0; i < pix->num_planes; i++) { + next_out_img.pixelmap[i].pitch =3D pix->plane_fmt[i].bytesperline; + phys_addr =3D vb2_dma_contig_plane_dma_addr(vb, i); + /* address mapping: + * - DDR0: (CPU)0x0_8000_0000-0x0_FFFF_FFFF -> (HW)0x8000_0000-0xFFFF_FF= FF + * - DDR1: (CPU)0x8_8000_0000-0x8_FFFF_FFFF -> (HW)0x0000_0000-0x7FFF_FF= FF + */ + next_out_img.pixelmap[i].pmap_paddr =3D (phys_addr & 0x800000000UL) ? + (phys_addr & 0x7fffffff) : + (phys_addr & 0xffffffff); + } + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l2_set_img_transmission(viif_dev->ch, VIIF_L2ISP_POST_0,= VIIF_ISP_REGBUF_0, + HWD_VIIF_ENABLE, &viif_dev->img_area, + &viif_dev->out_process, &next_out_img); + VIIF_ISP_GUARD_END(viif_dev); + if (ret) + dev_err(viif_dev->dev, "set img error. %d\n", ret); + + return ret; +} + +void visconti_viif_capture_switch_buffer(struct viif_device *viif_dev, uin= t32_t status_err, + uint32_t l2_transfer_status) +{ + struct vb2_v4l2_buffer *vbuf; + struct viif_buffer *buf; + enum vb2_buffer_state state; + + vbuf =3D viif_dev->dma_active; + if (!vbuf) + goto next; + + viif_dev->buf_cnt--; + vbuf->vb2_buf.timestamp =3D ktime_get_ns(); + vbuf->sequence =3D viif_dev->sequence++; + vbuf->field =3D viif_dev->field; + if (status_err || l2_transfer_status) + state =3D VB2_BUF_STATE_ERROR; + else + state =3D VB2_BUF_STATE_DONE; + + vb2_buffer_done(&vbuf->vb2_buf, state); + viif_dev->dma_active =3D NULL; + +next: + vbuf =3D viif_dev->active; + if (!vbuf) + return; + + if (viif_dev->last_active) { + viif_dev->dma_active =3D viif_dev->last_active; + viif_dev->last_active =3D NULL; + } else if (!viif_dev->dma_active) { + viif_dev->dma_active =3D vbuf; + buf =3D vb2_to_viif(vbuf); + list_del_init(&buf->queue); + } + + if (!list_empty(&viif_dev->capture)) { + buf =3D list_entry(viif_dev->capture.next, struct viif_buffer, queue); + viif_dev->active =3D &buf->vb; + viif_set_img(viif_dev, &buf->vb.vb2_buf); + } else { + dev_dbg(viif_dev->dev, "no queue\n"); + viif_dev->last_active =3D viif_dev->dma_active; + viif_dev->dma_active =3D NULL; + viif_dev->active =3D NULL; + } +} + +/* --- Capture buffer control --- */ +static int viif_vb2_setup(struct vb2_queue *vq, unsigned int *count, unsig= ned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct viif_device *viif_dev =3D vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix =3D &viif_dev->v4l2_pix; + unsigned int i; + + /* num_planes is set: just check plane sizes. */ + if (*num_planes) { + for (i =3D 0; i < pix->num_planes; i++) + if (sizes[i] < pix->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + /* num_planes not set: called from REQBUFS, just set plane sizes. */ + *num_planes =3D pix->num_planes; + for (i =3D 0; i < pix->num_planes; i++) + sizes[i] =3D pix->plane_fmt[i].sizeimage; + + viif_dev->buf_cnt =3D 0; + + return 0; +} + +static void viif_vb2_queue(struct vb2_buffer *vb) +{ + struct viif_device *viif_dev =3D vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct viif_buffer *buf =3D vb2_to_viif(vbuf); + unsigned long irqflags; + + spin_lock_irqsave(&viif_dev->lock, irqflags); + list_add_tail(&buf->queue, &viif_dev->capture); + viif_dev->buf_cnt++; + + if (!viif_dev->active) { + viif_dev->active =3D vbuf; + if (!viif_dev->last_active) + viif_set_img(viif_dev, vb); + } + spin_unlock_irqrestore(&viif_dev->lock, irqflags); +} + +static int viif_vb2_prepare(struct vb2_buffer *vb) +{ + struct viif_device *viif_dev =3D vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format_mplane *pix =3D &viif_dev->v4l2_pix; + unsigned int i; + + for (i =3D 0; i < pix->num_planes; i++) { + if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) { + dev_err(viif_dev->dev, "Plane size too small (%lu < %u)\n", + vb2_plane_size(vb, i), pix->plane_fmt[i].sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage); + } + return 0; +} +static int viif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct viif_device *viif_dev =3D vb2_get_drv_priv(vq); + struct viif_subdev *viif_sd =3D viif_dev->sd; + int ret; + unsigned long irqflags; + + spin_lock_irqsave(&viif_dev->lock, irqflags); + + ret =3D media_pipeline_start(&viif_dev->vdev.entity, &viif_dev->pipe); + if (ret) { + dev_err(viif_dev->dev, "start pipeline failed %d\n", ret); + } + + /* CSI2RX start */ + ret =3D v4l2_subdev_call(&viif_dev->isp_subdev.sd, video, s_stream, 1); + if (ret) { + dev_err(viif_dev->dev, "Start isp subdevice stream failed. %d\n", ret); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + return ret; + } + + /* buffer control */ + viif_dev->sequence =3D 0; + + /* finish critical section: some sensor driver (including imx219) calls s= chedule() */ + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + /* Camera (CSI2 source) start streaming */ + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, video, s_stream, 1); + if (ret) { + dev_err(viif_dev->dev, "Start subdev stream failed. %d\n", ret); + (void)v4l2_subdev_call(&viif_dev->isp_subdev.sd, video, s_stream, 0); + return ret; + } + + return 0; +} + +static void viif_stop_streaming(struct vb2_queue *vq) +{ + struct viif_device *viif_dev =3D vb2_get_drv_priv(vq); + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct viif_buffer *buf; + unsigned long irqflags; + int ret; + + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, video, s_stream, 0); + if (ret) + dev_err(viif_dev->dev, "Stop subdev stream failed. %d\n", ret); + + spin_lock_irqsave(&viif_dev->lock, irqflags); + + ret =3D v4l2_subdev_call(&viif_dev->isp_subdev.sd, video, s_stream, 0); + if (ret) + dev_err(viif_dev->dev, "Stop isp subdevice stream failed %d\n", ret); + + /* buffer control */ + viif_dev->active =3D NULL; + if (viif_dev->dma_active) { + vb2_buffer_done(&viif_dev->dma_active->vb2_buf, VB2_BUF_STATE_ERROR); + viif_dev->buf_cnt--; + viif_dev->dma_active =3D NULL; + } + if (viif_dev->last_active) { + vb2_buffer_done(&viif_dev->last_active->vb2_buf, VB2_BUF_STATE_ERROR); + viif_dev->buf_cnt--; + viif_dev->last_active =3D NULL; + } + + /* Release all queued buffers. */ + list_for_each_entry (buf, &viif_dev->capture, queue) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + viif_dev->buf_cnt--; + } + INIT_LIST_HEAD(&viif_dev->capture); + if (viif_dev->buf_cnt) + dev_err(viif_dev->dev, "Buffer count error %d\n", viif_dev->buf_cnt); + + media_pipeline_stop(&viif_dev->vdev.entity); + + spin_unlock_irqrestore(&viif_dev->lock, irqflags); +} + +static const struct vb2_ops viif_vb2_ops =3D { + .queue_setup =3D viif_vb2_setup, + .buf_queue =3D viif_vb2_queue, + .buf_prepare =3D viif_vb2_prepare, + .wait_prepare =3D vb2_ops_wait_prepare, + .wait_finish =3D vb2_ops_wait_finish, + .start_streaming =3D viif_start_streaming, + .stop_streaming =3D viif_stop_streaming, +}; + +/* --- VIIF hardware settings --- */ +extern int viif_isp_main_set_unit(struct viif_device *viif_dev); + +/* L2ISP output csc setting for YUV to RGB(ITU-R BT.709) */ +static const struct hwd_viif_csc_param viif_csc_yuv2rgb =3D { + .r_cr_in_offset =3D 0x18000, + .g_y_in_offset =3D 0x1f000, + .b_cb_in_offset =3D 0x18000, + .coef =3D { + [0] =3D 0x1000, + [1] =3D 0xfd12, + [2] =3D 0xf8ad, + [3] =3D 0x1000, + [4] =3D 0x1d07, + [5] =3D 0x0000, + [6] =3D 0x1000, + [7] =3D 0x0000, + [8] =3D 0x18a2, + }, + .r_cr_out_offset =3D 0x1000, + .g_y_out_offset =3D 0x1000, + .b_cb_out_offset =3D 0x1000, +}; + +/* L2ISP output csc setting for RGB to YUV(ITU-R BT.709) */ +static const struct hwd_viif_csc_param viif_csc_rgb2yuv =3D { + .r_cr_in_offset =3D 0x1f000, + .g_y_in_offset =3D 0x1f000, + .b_cb_in_offset =3D 0x1f000, + .coef =3D { + [0] =3D 0x0b71, + [1] =3D 0x0128, + [2] =3D 0x0367, + [3] =3D 0xf9b1, + [4] =3D 0x082f, + [5] =3D 0xfe20, + [6] =3D 0xf891, + [7] =3D 0xff40, + [8] =3D 0x082f, + }, + .r_cr_out_offset =3D 0x8000, + .g_y_out_offset =3D 0x1000, + .b_cb_out_offset =3D 0x8000, +}; + +static int viif_l2_set_format(struct viif_device *viif_dev) +{ + struct v4l2_pix_format_mplane *pix =3D &viif_dev->v4l2_pix; + const struct hwd_viif_csc_param *csc_param =3D NULL; + struct v4l2_subdev_selection sel =3D { + .pad =3D VIIF_ISP_PAD_SRC, + .target =3D V4L2_SEL_TGT_CROP, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev_format fmt =3D { + .pad =3D VIIF_ISP_PAD_SRC, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + bool inp_is_rgb =3D false; + bool out_is_rgb =3D false; + int ret; + + viif_dev->out_process.half_scale =3D HWD_VIIF_DISABLE; + viif_dev->out_process.select_color =3D HWD_VIIF_COLOR_YUV_RGB; + viif_dev->out_process.alpha =3D 0; + + ret =3D v4l2_subdev_call(&viif_dev->isp_subdev.sd, pad, get_selection, NU= LL, &sel); + if (ret) { + viif_dev->img_area.x =3D 0; + viif_dev->img_area.y =3D 0; + viif_dev->img_area.w =3D pix->width; + viif_dev->img_area.h =3D pix->height; + } else { + viif_dev->img_area.x =3D sel.r.left; + viif_dev->img_area.y =3D sel.r.top; + viif_dev->img_area.w =3D sel.r.width; + viif_dev->img_area.h =3D sel.r.height; + } + + ret =3D v4l2_subdev_call(&viif_dev->isp_subdev.sd, pad, get_fmt, NULL, &f= mt); + if (!ret) + inp_is_rgb =3D (fmt.format.code =3D=3D MEDIA_BUS_FMT_RGB888_1X24); + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_RGB24: + viif_dev->out_format =3D HWD_VIIF_RGB888_PACKED; + out_is_rgb =3D true; + break; + case V4L2_PIX_FMT_ABGR32: + viif_dev->out_format =3D HWD_VIIF_ARGB8888_PACKED; + viif_dev->out_process.alpha =3D 0xff; + out_is_rgb =3D true; + break; + case V4L2_PIX_FMT_YUV422M: + viif_dev->out_format =3D HWD_VIIF_YCBCR422_8_PLANAR; + break; + case V4L2_PIX_FMT_YUV444M: + viif_dev->out_format =3D HWD_VIIF_RGB888_YCBCR444_8_PLANAR; + break; + case V4L2_PIX_FMT_Y16: + viif_dev->out_format =3D HWD_VIIF_ONE_COLOR_16; + viif_dev->out_process.select_color =3D HWD_VIIF_COLOR_Y_G; + break; + } + + if (!inp_is_rgb && out_is_rgb) + csc_param =3D &viif_csc_yuv2rgb; /* YUV -> RGB */ + else if (inp_is_rgb && !out_is_rgb) + csc_param =3D &viif_csc_rgb2yuv; /* RGB -> YUV */ + + return hwd_VIIF_l2_set_output_csc(viif_dev->ch, VIIF_L2ISP_POST_0, VIIF_I= SP_REGBUF_0, + csc_param); +} + +int viif_l2_set_crop(struct viif_device *viif_dev, struct viif_l2_crop_con= fig *l2_crop) +{ + struct v4l2_subdev_selection sel =3D { + .pad =3D VIIF_ISP_PAD_SRC, + .target =3D V4L2_SEL_TGT_CROP, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + .r =3D { + .left =3D l2_crop->x, + .top =3D l2_crop->y, + .width =3D l2_crop->w, + .height =3D l2_crop->h, + }, + }; + + if ((l2_crop->x > VIIF_CROP_MAX_X_ISP) || (l2_crop->y > VIIF_CROP_MAX_Y_I= SP) || + (l2_crop->w < VIIF_CROP_MIN_W) || (l2_crop->w > VIIF_CROP_MAX_W_ISP) = || + (l2_crop->h < VIIF_CROP_MIN_H) || (l2_crop->h > VIIF_CROP_MAX_H_ISP))= { + return -EINVAL; + } + + return v4l2_subdev_call(&viif_dev->isp_subdev.sd, pad, set_selection, NUL= L, &sel); +} + +/* --- IOCTL Operations --- */ +static const struct viif_fmt viif_fmt_list[] =3D { + { + .fourcc =3D V4L2_PIX_FMT_RGB24, + .bpp =3D { 24, 0, 0 }, + .num_planes =3D 1, + .colorspace =3D V4L2_COLORSPACE_SRGB, + .pitch_align =3D 384, + }, + { + .fourcc =3D V4L2_PIX_FMT_ABGR32, + .bpp =3D { 32, 0, 0 }, + .num_planes =3D 1, + .colorspace =3D V4L2_COLORSPACE_SRGB, + .pitch_align =3D 512, + }, + { + .fourcc =3D V4L2_PIX_FMT_YUV422M, + .bpp =3D { 8, 4, 4 }, + .num_planes =3D 3, + .colorspace =3D V4L2_COLORSPACE_REC709, + .pitch_align =3D 128, + }, + { + .fourcc =3D V4L2_PIX_FMT_YUV444M, + .bpp =3D { 8, 8, 8 }, + .num_planes =3D 3, + .colorspace =3D V4L2_COLORSPACE_REC709, + .pitch_align =3D 128, + }, + { + .fourcc =3D V4L2_PIX_FMT_Y16, + .bpp =3D { 16, 0, 0 }, + .num_planes =3D 1, + .colorspace =3D V4L2_COLORSPACE_REC709, + .pitch_align =3D 128, + }, +}; + +static const struct viif_fmt *get_viif_fmt_from_fourcc(unsigned int fourcc) +{ + const struct viif_fmt *fmt =3D &viif_fmt_list[0]; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(viif_fmt_list); i++, fmt++) + if (fmt->fourcc =3D=3D fourcc) + return fmt; + + return NULL; +} + +static void viif_update_plane_sizes(struct v4l2_plane_pix_format *plane, u= nsigned int bpl, + unsigned int szimage) +{ + memset(plane, 0, sizeof(*plane)); + + plane->sizeimage =3D szimage; + plane->bytesperline =3D bpl; +} + +static void viif_calc_plane_sizes(const struct viif_fmt *viif_fmt, + struct v4l2_pix_format_mplane *pix) +{ + unsigned int i, bpl, szimage; + + for (i =3D 0; i < viif_fmt->num_planes; i++) { + bpl =3D pix->width * viif_fmt->bpp[i] / 8; + /* round up ptch */ + bpl =3D (bpl + (viif_fmt->pitch_align - 1)) / viif_fmt->pitch_align; + bpl *=3D viif_fmt->pitch_align; + szimage =3D pix->height * bpl; + viif_update_plane_sizes(&pix->plane_fmt[i], bpl, szimage); + } + pix->num_planes =3D viif_fmt->num_planes; +} + +static int viif_querycap(struct file *file, void *priv, struct v4l2_capabi= lity *cap) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + strscpy(cap->card, "Toshiba VIIF", sizeof(cap->card)); + strscpy(cap->driver, "viif", sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:toshiba-viif-%s", + dev_name(viif_dev->dev)); + return 0; +} + +static int viif_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l= 2_fmtdesc *f) +{ + const struct viif_fmt *fmt; + + if (f->index >=3D ARRAY_SIZE(viif_fmt_list)) + return -EINVAL; + + fmt =3D &viif_fmt_list[f->index]; + f->pixelformat =3D fmt->fourcc; + + return 0; +} + +/* size of minimum/maximum output image */ +#define VIIF_MIN_OUTPUT_IMG_WIDTH (128U) +#define VIIF_MAX_OUTPUT_IMG_WIDTH_ISP (5760U) +#define VIIF_MAX_OUTPUT_IMG_WIDTH_SUB (4096U) + +#define VIIF_MIN_OUTPUT_IMG_HEIGHT (128U) +#define VIIF_MAX_OUTPUT_IMG_HEIGHT_ISP (3240U) +#define VIIF_MAX_OUTPUT_IMG_HEIGHT_SUB (2160U) + +static int viif_try_fmt(struct viif_device *viif_dev, struct v4l2_format *= v4l2_fmt) +{ + struct v4l2_pix_format_mplane *pix =3D &v4l2_fmt->fmt.pix_mp; + struct v4l2_subdev_format format =3D { + .pad =3D VIIF_ISP_PAD_SRC, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct viif_fmt *viif_fmt; + int ret; + + /* fourcc check */ + viif_fmt =3D get_viif_fmt_from_fourcc(pix->pixelformat); + if (!viif_fmt) + return -EINVAL; + + /* min/max width, height check */ + if (pix->width < VIIF_MIN_OUTPUT_IMG_WIDTH) + pix->width =3D VIIF_MIN_OUTPUT_IMG_WIDTH; + + if (pix->width > VIIF_MAX_OUTPUT_IMG_WIDTH_ISP) + pix->width =3D VIIF_MAX_OUTPUT_IMG_WIDTH_ISP; + + if (pix->height < VIIF_MIN_OUTPUT_IMG_HEIGHT) + pix->height =3D VIIF_MIN_OUTPUT_IMG_HEIGHT; + + if (pix->height > VIIF_MAX_OUTPUT_IMG_HEIGHT_ISP) + pix->height =3D VIIF_MAX_OUTPUT_IMG_HEIGHT_ISP; + + /* experimental: consistency with isp::pad::src::fmt */ + ret =3D v4l2_subdev_call(&viif_dev->isp_subdev.sd, pad, get_fmt, NULL, &f= ormat); + if (ret) + return -EINVAL; + if (pix->width !=3D format.format.width) + return -EINVAL; + if (pix->height !=3D format.format.height) + return -EINVAL; + + /* update derived parameters, such as bpp */ + viif_calc_plane_sizes(viif_fmt, pix); + + return 0; +} + +static int viif_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2= _format *f) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + return viif_try_fmt(viif_dev, f); +} + +static int viif_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_f= ormat *f) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + int ret =3D 0; + + if (vb2_is_streaming(&viif_dev->vb2_vq)) + return -EBUSY; + + if (f->type !=3D viif_dev->vb2_vq.type) + return -EINVAL; + + ret =3D viif_try_fmt(viif_dev, f); + if (ret) + return ret; + + /* TODO: this function should be called from viif_isp_s_stream()?? */ + ret =3D viif_isp_main_set_unit(viif_dev); + if (ret) + return ret; + + viif_dev->v4l2_pix =3D f->fmt.pix_mp; + viif_dev->field =3D V4L2_FIELD_NONE; + + return viif_l2_set_format(viif_dev); +} + +static int viif_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_f= ormat *f) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + f->fmt.pix_mp =3D viif_dev->v4l2_pix; + + return 0; +} + +static int viif_enum_input(struct file *file, void *priv, struct v4l2_inpu= t *inp) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd; + struct v4l2_subdev *v4l2_sd; + int ret; + + if (inp->index >=3D viif_dev->num_sd) + return -EINVAL; + + viif_sd =3D &viif_dev->subdevs[inp->index]; + v4l2_sd =3D viif_sd->v4l2_sd; + + ret =3D v4l2_subdev_call(v4l2_sd, video, g_input_status, &inp->status); + if (ret < 0 && ret !=3D -ENOIOCTLCMD && ret !=3D -ENODEV) + return ret; + inp->type =3D V4L2_INPUT_TYPE_CAMERA; + inp->std =3D 0; + if (v4l2_subdev_has_op(v4l2_sd, pad, dv_timings_cap)) + inp->capabilities =3D V4L2_IN_CAP_DV_TIMINGS; + else + inp->capabilities =3D V4L2_IN_CAP_STD; + snprintf(inp->name, sizeof(inp->name), "Camera%u: %s", inp->index, viif_s= d->v4l2_sd->name); + + return 0; +} + +static int viif_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + *i =3D viif_dev->sd_index; + + return 0; +} + +static int viif_s_input(struct file *file, void *priv, unsigned int i) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + if (i >=3D viif_dev->num_sd) + return -EINVAL; + + return 0; +} + +static int viif_dv_timings_cap(struct file *file, void *priv_fh, struct v4= l2_dv_timings_cap *cap) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, pad, dv_timings_cap, cap); +} + +static int viif_enum_dv_timings(struct file *file, void *priv_fh, + struct v4l2_enum_dv_timings *timings) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, pad, enum_dv_timings, timings); +} + +static int viif_g_dv_timings(struct file *file, void *priv_fh, struct v4l2= _dv_timings *timings) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, video, g_dv_timings, timings); +} + +static int viif_s_dv_timings(struct file *file, void *priv_fh, struct v4l2= _dv_timings *timings) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, video, s_dv_timings, timings); +} + +static int viif_query_dv_timings(struct file *file, void *priv_fh, struct = v4l2_dv_timings *timings) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, video, query_dv_timings, timing= s); +} + +static int viif_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, pad, get_edid, edid); +} + +static int viif_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + + return v4l2_subdev_call(viif_sd->v4l2_sd, pad, set_edid, edid); +} + +static int viif_g_parm(struct file *file, void *fh, struct v4l2_streamparm= *a) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), viif_dev->sd->v4l2_sd, a); +} + +static int viif_s_parm(struct file *file, void *fh, struct v4l2_streamparm= *a) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), viif_dev->sd->v4l2_sd, a); +} + +static int viif_enum_framesizes(struct file *file, void *fh, struct v4l2_f= rmsizeenum *fsize) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct v4l2_subdev *v4l2_sd =3D viif_sd->v4l2_sd; + struct v4l2_subdev_frame_size_enum fse =3D { + .code =3D viif_sd->mbus_code, + .index =3D fsize->index, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + ret =3D v4l2_subdev_call(v4l2_sd, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + + fsize->type =3D V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width =3D fse.max_width; + fsize->discrete.height =3D fse.max_height; + + return 0; +} + +static int viif_enum_frameintervals(struct file *file, void *fh, struct v4= l2_frmivalenum *fival) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct v4l2_subdev *v4l2_sd =3D viif_sd->v4l2_sd; + struct v4l2_subdev_frame_interval_enum fie =3D { + .code =3D viif_sd->mbus_code, + .index =3D fival->index, + .width =3D fival->width, + .height =3D fival->height, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + ret =3D v4l2_subdev_call(v4l2_sd, pad, enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type =3D V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete =3D fie.interval; + + return 0; +} + +static const struct v4l2_ioctl_ops viif_ioctl_ops =3D { + .vidioc_querycap =3D viif_querycap, + + .vidioc_enum_fmt_vid_cap =3D viif_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane =3D viif_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane =3D viif_s_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane =3D viif_g_fmt_vid_cap, + + .vidioc_enum_input =3D viif_enum_input, + .vidioc_g_input =3D viif_g_input, + .vidioc_s_input =3D viif_s_input, + + .vidioc_dv_timings_cap =3D viif_dv_timings_cap, + .vidioc_enum_dv_timings =3D viif_enum_dv_timings, + .vidioc_g_dv_timings =3D viif_g_dv_timings, + .vidioc_s_dv_timings =3D viif_s_dv_timings, + .vidioc_query_dv_timings =3D viif_query_dv_timings, + + .vidioc_g_edid =3D viif_g_edid, + .vidioc_s_edid =3D viif_s_edid, + + .vidioc_g_parm =3D viif_g_parm, + .vidioc_s_parm =3D viif_s_parm, + + .vidioc_enum_framesizes =3D viif_enum_framesizes, + .vidioc_enum_frameintervals =3D viif_enum_frameintervals, + + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_expbuf =3D vb2_ioctl_expbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_prepare_buf =3D vb2_ioctl_prepare_buf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, + + .vidioc_log_status =3D v4l2_ctrl_log_status, + .vidioc_subscribe_event =3D v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event =3D v4l2_event_unsubscribe, +}; + +/* --- File Operations --- */ +void viif_hw_on(struct viif_device *viif_dev); +void viif_hw_off(struct viif_device *viif_dev); + +static int viif_capture_open(struct file *file) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + int ret, mask; + + ret =3D v4l2_fh_open(file); + if (ret) + return ret; + + viif_dev->rawpack_mode =3D HWD_VIIF_RAWPACK_DISABLE; + + viif_dev->is_powered =3D 1; + + mutex_lock(&viif_dev->mlock); + + /* Initialize HWD driver */ + viif_hw_on(viif_dev); + + /* VSYNC mask setting of MAIN unit */ + mask =3D 0x00050003; + hwd_VIIF_main_vsync_set_irq_mask(viif_dev->ch, &mask); + + /* STATUS error mask setting(unmask) of MAIN unit */ + mask =3D 0x01000000; + hwd_VIIF_main_status_err_set_irq_mask(viif_dev->ch, &mask); + + mutex_unlock(&viif_dev->mlock); + + return ret; +} + +static int viif_capture_release(struct file *file) +{ + struct viif_device *viif_dev =3D video_drvdata(file); + int ret; + + ret =3D vb2_fop_release(file); + if (ret) + return ret; + + mutex_lock(&viif_dev->mlock); + viif_hw_off(viif_dev); + mutex_unlock(&viif_dev->mlock); + + viif_dev->is_powered =3D 0; + + return 0; +} + +static const struct v4l2_file_operations viif_fops =3D { + .owner =3D THIS_MODULE, + .open =3D viif_capture_open, + .release =3D viif_capture_release, + .unlocked_ioctl =3D video_ioctl2, + .mmap =3D vb2_fop_mmap, + .poll =3D vb2_fop_poll, +}; + +/* ----- media control callbacks ----- */ +static int viif_capture_link_validate(struct media_link *link) +{ + /* TODO: add link validation at start-stream */ + pr_info("viif_capture_link_validate called\n"); + return 0; +} + +static const struct media_entity_operations viif_media_ops =3D { + .link_validate =3D viif_capture_link_validate, +}; + +/* ----- register/remove capture device node ----- */ +int visconti_viif_capture_register(struct viif_device *viif_dev) +{ + struct v4l2_device *v4l2_dev =3D &viif_dev->v4l2_dev; + struct video_device *vdev =3D &viif_dev->vdev; + struct vb2_queue *q =3D &viif_dev->vb2_vq; + int ret; + + /* Initialize vb2 queue. */ + q->type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes =3D VB2_DMABUF; + q->timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->ops =3D &viif_vb2_ops; + q->mem_ops =3D &vb2_dma_contig_memops; + q->drv_priv =3D viif_dev; + q->buf_struct_size =3D sizeof(struct viif_buffer); + q->min_buffers_needed =3D 2; + q->lock =3D &viif_dev->mlock; + q->dev =3D viif_dev->v4l2_dev.dev; + + ret =3D vb2_queue_init(q); + if (ret) + return ret; + + /* Register the video device. */ + strscpy(vdev->name, "viif_capture", sizeof(vdev->name)); + vdev->v4l2_dev =3D v4l2_dev; + vdev->lock =3D &viif_dev->mlock; + vdev->queue =3D &viif_dev->vb2_vq; + vdev->ctrl_handler =3D NULL; + vdev->fops =3D &viif_fops; + vdev->ioctl_ops =3D &viif_ioctl_ops; + vdev->device_caps =3D V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING; + vdev->device_caps |=3D V4L2_CAP_IO_MC; + vdev->entity.ops =3D &viif_media_ops; + vdev->release =3D video_device_release_empty; + video_set_drvdata(vdev, viif_dev); + vdev->vfl_dir =3D VFL_DIR_RX; + viif_dev->capture_pad.flags =3D MEDIA_PAD_FL_SINK; + + ret =3D video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(v4l2_dev->dev, "video_register_device failed: %d\n", ret); + return ret; + } + + ret =3D media_entity_pads_init(&vdev->entity, 1, &viif_dev->capture_pad); + if (ret) { + video_unregister_device(vdev); + return ret; + } + + return 0; +} + +void visconti_viif_capture_unregister(struct viif_device *viif_dev) +{ + media_entity_cleanup(&viif_dev->vdev.entity); + vb2_video_unregister_device(&viif_dev->vdev); +} diff --git a/drivers/media/platform/visconti/viif_ioctl.c b/drivers/media/p= latform/visconti/viif_ioctl.c new file mode 100644 index 000000000..75a4bb83f --- /dev/null +++ b/drivers/media/platform/visconti/viif_ioctl.c @@ -0,0 +1,287 @@ +#include +#include +#include + +#include "viif.h" + +#define VIIF_ISP_GUARD_START(viif_dev) = \ + do { = \ + hwd_VIIF_isp_disable_regbuf_auto_transmission(viif_dev->ch); = \ + ndelay(500); = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_ENABLE); = \ + } while (0) + +#define VIIF_ISP_GUARD_END(viif_dev) = \ + do { = \ + hwd_VIIF_main_mask_vlatch(viif_dev->ch, HWD_VIIF_DISABLE); = \ + hwd_VIIF_isp_set_regbuf_auto_transmission(viif_dev->ch, VIIF_ISP_REGBUF_= 0, \ + VIIF_ISP_REGBUF_0, 0); \ + } while (0) + +int viif_l1_set_input_mode(struct viif_device *viif_dev, + struct viif_l1_input_mode_config *input_mode) +{ + int ret; + unsigned long irqflags; + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + /* SDR input is not supported */ + ret =3D hwd_VIIF_l1_set_input_mode(viif_dev->ch, input_mode->mode, input_= mode->depth, + input_mode->raw_color_filter, NULL); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +int viif_l1_set_main_process(struct viif_device *viif_dev, struct viif_l1_= main_process_config *mpro) +{ + struct hwd_viif_l1_color_matrix_correction color_matrix; + int ret; + unsigned long irqflags; + + if (mpro->param) { + if (copy_from_user(&color_matrix, (void __user *)mpro->param, + sizeof(struct hwd_viif_l1_color_matrix_correction))) + return -EFAULT; + } + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l1_set_main_process(viif_dev->ch, VIIF_ISP_REGBUF_0, mpr= o->demosaic_mode, + mpro->damp_lsbsel, mpro->param ? &color_matrix : NULL, + mpro->dst_maxval); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +int viif_l1_set_black_level_correction(struct viif_device *viif_dev, + struct viif_l1_black_level_correction_config *blc) +{ + int ret; + unsigned long irqflags; + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l1_set_black_level_correction( + viif_dev->ch, VIIF_ISP_REGBUF_0, (struct hwd_viif_l1_black_level_correct= ion *)blc); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +int viif_l1_set_awb(struct viif_device *viif_dev, struct viif_l1_awb_confi= g *l1_awb) +{ + struct hwd_viif_l1_awb param; + int ret; + unsigned long irqflags; + + if (l1_awb->param) { + if (copy_from_user(¶m, (void __user *)l1_awb->param, + sizeof(struct hwd_viif_l1_awb))) + return -EFAULT; + } + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l1_set_awb(viif_dev->ch, VIIF_ISP_REGBUF_0, l1_awb->para= m ? ¶m : NULL, + l1_awb->awhb_wbmrg, l1_awb->awhb_wbmgg, l1_awb->awhb_wbmbg); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +int viif_l1_set_hdrc(struct viif_device *viif_dev, struct viif_l1_hdrc_con= fig *hdrc) +{ + struct hwd_viif_l1_hdrc param; + int ret; + unsigned long irqflags; + + if (hdrc->param) { + if (copy_from_user(¶m, (void __user *)hdrc->param, + sizeof(struct hwd_viif_l1_hdrc))) + return -EFAULT; + } + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l1_set_hdrc(viif_dev->ch, VIIF_ISP_REGBUF_0, hdrc->param= ? ¶m : NULL, + hdrc->hdrc_thr_sft_amt); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +int viif_l1_set_img_quality_adjustment(struct viif_device *viif_dev, + struct viif_l1_img_quality_adjustment_config *img_quality) +{ + struct viif_l1_nonlinear_contrast nonlinear; + struct viif_l1_lum_noise_reduction lum_noise; + struct viif_l1_edge_enhancement edge_enh; + struct viif_l1_uv_suppression uv; + struct viif_l1_coring_suppression coring; + struct viif_l1_edge_suppression edge_sup; + struct viif_l1_color_level color; + int ret; + unsigned long irqflags; + + if (img_quality->nonlinear_contrast) { + if (copy_from_user(&nonlinear, (void __user *)img_quality->nonlinear_con= trast, + sizeof(struct viif_l1_nonlinear_contrast))) + return -EFAULT; + img_quality->nonlinear_contrast =3D &nonlinear; + } + if (img_quality->lum_noise_reduction) { + if (copy_from_user(&lum_noise, (void __user *)img_quality->lum_noise_red= uction, + sizeof(struct viif_l1_lum_noise_reduction))) + return -EFAULT; + img_quality->lum_noise_reduction =3D &lum_noise; + } + if (img_quality->edge_enhancement) { + if (copy_from_user(&edge_enh, (void __user *)img_quality->edge_enhanceme= nt, + sizeof(struct viif_l1_edge_enhancement))) + return -EFAULT; + img_quality->edge_enhancement =3D &edge_enh; + } + if (img_quality->uv_suppression) { + if (copy_from_user(&uv, (void __user *)img_quality->uv_suppression, + sizeof(struct viif_l1_uv_suppression))) + return -EFAULT; + img_quality->uv_suppression =3D &uv; + } + if (img_quality->coring_suppression) { + if (copy_from_user(&coring, (void __user *)img_quality->coring_suppressi= on, + sizeof(struct viif_l1_coring_suppression))) + return -EFAULT; + img_quality->coring_suppression =3D &coring; + } + if (img_quality->edge_suppression) { + if (copy_from_user(&edge_sup, (void __user *)img_quality->edge_suppressi= on, + sizeof(struct viif_l1_edge_suppression))) + return -EFAULT; + img_quality->edge_suppression =3D &edge_sup; + } + if (img_quality->color_level) { + if (copy_from_user(&color, (void __user *)img_quality->color_level, + sizeof(struct viif_l1_color_level))) + return -EFAULT; + img_quality->color_level =3D &color; + } + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l1_set_img_quality_adjustment( + viif_dev->ch, VIIF_ISP_REGBUF_0, + (struct hwd_viif_l1_img_quality_adjustment *)img_quality); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + + return ret; +} + +#define VISCONTI_VIIF_DPC_TABLE_SIZE_MIN 1024 +#define VISCONTI_VIIF_DPC_TABLE_SIZE_MAX 8192 +int viif_l2_set_undist(struct viif_device *viif_dev, struct viif_l2_undist= _config *undist) +{ + int ret; + unsigned long irqflags; + uintptr_t table_write_g_paddr =3D 0; + uintptr_t table_read_b_paddr =3D 0; + uintptr_t table_read_g_paddr =3D 0; + uintptr_t table_read_r_paddr =3D 0; + + if ((undist->size && (undist->size < VISCONTI_VIIF_DPC_TABLE_SIZE_MIN)) || + (undist->size > VISCONTI_VIIF_DPC_TABLE_SIZE_MAX)) + return -EINVAL; + + if (undist->write_g) { + if (copy_from_user(viif_dev->table_vaddr->undist_write_g, + (void __user *)undist->write_g, undist->size)) + return -EFAULT; + table_write_g_paddr =3D (uintptr_t)viif_dev->table_paddr->undist_write_g; + } + if (undist->read_b) { + if (copy_from_user(viif_dev->table_vaddr->undist_read_b, + (void __user *)undist->read_b, undist->size)) + return -EFAULT; + table_read_b_paddr =3D (uintptr_t)viif_dev->table_paddr->undist_read_b; + } + if (undist->read_g) { + if (copy_from_user(viif_dev->table_vaddr->undist_read_g, + (void __user *)undist->read_g, undist->size)) + return -EFAULT; + table_read_g_paddr =3D (uintptr_t)viif_dev->table_paddr->undist_read_g; + } + if (undist->read_r) { + if (copy_from_user(viif_dev->table_vaddr->undist_read_r, + (void __user *)undist->read_r, undist->size)) + return -EFAULT; + table_read_r_paddr =3D (uintptr_t)viif_dev->table_paddr->undist_read_r; + } + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l2_set_undist_table_transmission(viif_dev->ch, table_wri= te_g_paddr, + table_read_b_paddr, table_read_g_paddr, + table_read_r_paddr, undist->size); + if (ret) { + dev_err(viif_dev->dev, "l2_set_undist_table_transmission error. %d\n", r= et); + goto err; + } + + ret =3D hwd_VIIF_l2_set_undist(viif_dev->ch, VIIF_ISP_REGBUF_0, + (struct hwd_viif_l2_undist *)&undist->param); +err: + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + return ret; +} + +int viif_l2_set_roi(struct viif_device *viif_dev, struct viif_l2_roi_confi= g *roi) +{ + int ret; + unsigned long irqflags; + + spin_lock_irqsave(&viif_dev->lock, irqflags); + VIIF_ISP_GUARD_START(viif_dev); + ret =3D hwd_VIIF_l2_set_roi(viif_dev->ch, VIIF_ISP_REGBUF_0, (struct hwd_= viif_l2_roi *)roi); + VIIF_ISP_GUARD_END(viif_dev); + spin_unlock_irqrestore(&viif_dev->lock, irqflags); + return ret; +} + +int viif_csi2rx_get_calibration_status( + struct viif_device *viif_dev, + struct viif_csi2rx_dphy_calibration_status *calibration_status) +{ + int ret; + + if (!vb2_is_streaming(&viif_dev->vb2_vq)) + return -EIO; + + ret =3D hwd_VIIF_csi2rx_get_calibration_status( + viif_dev->ch, (struct hwd_viif_csi2rx_dphy_calibration_status *)calibrat= ion_status); + + return ret; +} + +int viif_csi2rx_get_err_status(struct viif_device *viif_dev, struct viif_c= si2rx_err_status *csi_err) +{ + int ret; + + if (!vb2_is_streaming(&viif_dev->vb2_vq)) + return -EIO; + + ret =3D hwd_VIIF_csi2rx_get_err_status(viif_dev->ch, &csi_err->err_phy_fa= tal, + &csi_err->err_pkt_fatal, &csi_err->err_frame_fatal, + &csi_err->err_phy, &csi_err->err_pkt, + &csi_err->err_line); + + return ret; +} diff --git a/drivers/media/platform/visconti/viif_isp.c b/drivers/media/pla= tform/visconti/viif_isp.c new file mode 100644 index 000000000..e271dff15 --- /dev/null +++ b/drivers/media/platform/visconti/viif_isp.c @@ -0,0 +1,968 @@ +#include +#include + +#include "viif.h" + +/* ----- supported MBUS formats ----- */ +struct visconti_mbus_format { + unsigned int code; + unsigned int bpp; + int rgb_out; +} static visconti_mbus_formats[] =3D { + { .code =3D MEDIA_BUS_FMT_RGB888_1X24, .bpp =3D 24, .rgb_out =3D 1 }, + { .code =3D MEDIA_BUS_FMT_UYVY8_1X16, .bpp =3D 16, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_UYVY10_1X20, .bpp =3D 20, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_RGB565_1X16, .bpp =3D 16, .rgb_out =3D 1 }, + { .code =3D MEDIA_BUS_FMT_SBGGR8_1X8, .bpp =3D 8, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGBRG8_1X8, .bpp =3D 8, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGRBG8_1X8, .bpp =3D 8, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SRGGB8_1X8, .bpp =3D 8, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SRGGB10_1X10, .bpp =3D 10, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGRBG10_1X10, .bpp =3D 10, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGBRG10_1X10, .bpp =3D 10, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SBGGR10_1X10, .bpp =3D 10, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SRGGB12_1X12, .bpp =3D 12, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGRBG12_1X12, .bpp =3D 12, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGBRG12_1X12, .bpp =3D 12, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SBGGR12_1X12, .bpp =3D 12, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SRGGB14_1X14, .bpp =3D 14, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGRBG14_1X14, .bpp =3D 14, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SGBRG14_1X14, .bpp =3D 14, .rgb_out =3D 0 }, + { .code =3D MEDIA_BUS_FMT_SBGGR14_1X14, .bpp =3D 14, .rgb_out =3D 0 }, +}; + +static int viif_get_mbus_rgb_out(unsigned int mbus_code) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(visconti_mbus_formats); i++) + if (visconti_mbus_formats[i].code =3D=3D mbus_code) + return visconti_mbus_formats[i].rgb_out; + + /* YUV intermediate code by default */ + return 0; +} + +static unsigned int viif_get_mbus_bpp(unsigned int mbus_code) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(visconti_mbus_formats); i++) + if (visconti_mbus_formats[i].code =3D=3D mbus_code) + return visconti_mbus_formats[i].bpp; + + /* default bpp value */ + return 24; +} + +static bool viif_is_valid_mbus_code(unsigned int mbus_code) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(visconti_mbus_formats); i++) + if (visconti_mbus_formats[i].code =3D=3D mbus_code) + return true; + return false; +} + +/* ----- handling main processing path ----- */ +static int viif_get_dv_timings(struct viif_device *viif_dev, struct v4l2_d= v_timings *timings) +{ + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct v4l2_ctrl *ctrl; + int ret; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_state pad_state =3D { + .pads =3D &pad_cfg, + }; + struct v4l2_subdev_format format =3D { + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + .pad =3D 0, + }; + + /* some video I/F support dv_timings query */ + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, video, g_dv_timings, timings); + if (ret =3D=3D 0) + return 0; + + /* others: call some discrete APIs */ + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, pad, get_fmt, &pad_state, &for= mat); + if (ret !=3D 0) + return ret; + + timings->bt.width =3D format.format.width; + timings->bt.height =3D format.format.height; + + ctrl =3D v4l2_ctrl_find(viif_sd->v4l2_sd->ctrl_handler, V4L2_CID_HBLANK); + if (!ctrl) { + dev_err(viif_dev->dev, "subdev: V4L2_CID_VBLANK error.\n"); + return -EINVAL; + } + timings->bt.hsync =3D v4l2_ctrl_g_ctrl(ctrl); + + ctrl =3D v4l2_ctrl_find(viif_sd->v4l2_sd->ctrl_handler, V4L2_CID_VBLANK); + if (!ctrl) { + dev_err(viif_dev->dev, "subdev: V4L2_CID_VBLANK error.\n"); + return -EINVAL; + } + timings->bt.vsync =3D v4l2_ctrl_g_ctrl(ctrl); + + ctrl =3D v4l2_ctrl_find(viif_sd->v4l2_sd->ctrl_handler, V4L2_CID_PIXEL_RA= TE); + if (!ctrl) { + dev_err(viif_dev->dev, "subdev: V4L2_CID_PIXEL_RATE error.\n"); + return -EINVAL; + } + timings->bt.pixelclock =3D v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +/*TODO: should be moved below visconti_viif_isp_s_stream()?? */ +int viif_isp_main_set_unit(struct viif_device *viif_dev) +{ + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct v4l2_dv_timings timings; + struct v4l2_subdev_format fmt =3D { + .pad =3D 0, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + unsigned int dt_image, color_type, rawpack, yuv_conv; + struct hwd_viif_input_img in_img_main; + int ret =3D 0; + int mag_hactive =3D 1; + struct hwd_viif_l2_undist undist =3D { 0 }; + + ret =3D viif_get_dv_timings(viif_dev, &timings); + if (ret) { + dev_err(viif_dev->dev, "could not get timing information of subdev"); + return -EINVAL; + } + + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, pad, get_fmt, NULL, &fmt); + if (ret) { + dev_err(viif_dev->dev, "could not get pad information of subdev"); + return -EINVAL; + } + + switch (fmt.format.code) { + case MEDIA_BUS_FMT_RGB888_1X24: + dt_image =3D VISCONTI_CSI2_DT_RGB888; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + dt_image =3D VISCONTI_CSI2_DT_YUV4228B; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + dt_image =3D VISCONTI_CSI2_DT_YUV42210B; + break; + case MEDIA_BUS_FMT_RGB565_1X16: + dt_image =3D VISCONTI_CSI2_DT_RGB565; + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + dt_image =3D VISCONTI_CSI2_DT_RAW8; + break; + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + dt_image =3D VISCONTI_CSI2_DT_RAW10; + break; + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SBGGR12_1X12: + dt_image =3D VISCONTI_CSI2_DT_RAW12; + break; + case MEDIA_BUS_FMT_SRGGB14_1X14: + case MEDIA_BUS_FMT_SGRBG14_1X14: + case MEDIA_BUS_FMT_SGBRG14_1X14: + case MEDIA_BUS_FMT_SBGGR14_1X14: + dt_image =3D VISCONTI_CSI2_DT_RAW14; + break; + default: + dt_image =3D VISCONTI_CSI2_DT_RGB888; + break; + } + + color_type =3D dt_image; + + if ((color_type =3D=3D VISCONTI_CSI2_DT_RAW8) || (color_type =3D=3D VISCO= NTI_CSI2_DT_RAW10) || + (color_type =3D=3D VISCONTI_CSI2_DT_RAW12)) { + rawpack =3D viif_dev->rawpack_mode; + if (rawpack !=3D HWD_VIIF_RAWPACK_DISABLE) + mag_hactive =3D 2; + } else + rawpack =3D HWD_VIIF_RAWPACK_DISABLE; + + if ((color_type =3D=3D VISCONTI_CSI2_DT_YUV4228B) || (color_type =3D=3D V= ISCONTI_CSI2_DT_YUV42210B)) + yuv_conv =3D HWD_VIIF_YUV_CONV_INTERPOLATION; + else + yuv_conv =3D HWD_VIIF_YUV_CONV_REPEAT; + + in_img_main.hactive_size =3D timings.bt.width; + in_img_main.vactive_size =3D timings.bt.height; + in_img_main.htotal_size =3D timings.bt.width * mag_hactive + timings.bt.h= sync; + in_img_main.vtotal_size =3D timings.bt.height + timings.bt.vsync; + in_img_main.pixel_clock =3D timings.bt.pixelclock / 1000; + in_img_main.vbp_size =3D timings.bt.vsync - 5; + + in_img_main.interpolation_mode =3D HWD_VIIF_L1_INPUT_INTERPOLATION_LINE; + in_img_main.input_num =3D 1; + in_img_main.hobc_width =3D 0; + in_img_main.hobc_margin =3D 0; + + /* configuration of MAIN unit */ + ret =3D hwd_VIIF_main_set_unit(viif_dev->ch, dt_image, 0, &in_img_main, c= olor_type, rawpack, + yuv_conv); + if (ret) { + dev_err(viif_dev->dev, "main_set_unit error. %d\n", ret); + return ret; + } + + /* Enable regbuf */ + hwd_VIIF_isp_set_regbuf_auto_transmission(viif_dev->ch, VIIF_ISP_REGBUF_0, + VIIF_ISP_REGBUF_0, 0); + + /* L2 UNDIST Enable through mode as default */ + undist.through_mode =3D HWD_VIIF_ENABLE; + undist.sensor_crop_ofs_h =3D 1 - in_img_main.hactive_size; + undist.sensor_crop_ofs_v =3D 1 - in_img_main.vactive_size; + undist.grid_node_num_h =3D 16; + undist.grid_node_num_v =3D 16; + ret =3D hwd_VIIF_l2_set_undist(viif_dev->ch, VIIF_ISP_REGBUF_0, &undist); + if (ret) + dev_err(viif_dev->dev, "l2_set_undist error. %d\n", ret); + return ret; +} + +/* ----- handling CSI2RX hardware ----- */ +static int viif_csi2rx_initialize(struct viif_device *viif_dev) +{ + struct viif_subdev *viif_sd =3D viif_dev->sd; + struct hwd_viif_csi2rx_line_err_target err_target =3D { 0 }; + struct hwd_viif_csi2rx_irq_mask csi2rx_mask; + struct v4l2_mbus_config cfg =3D { 0 }; + struct v4l2_subdev_format fmt =3D { + .pad =3D 0, + .which =3D V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_dv_timings timings; + int num_lane, dphy_rate; + int ret; + + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, pad, get_mbus_config, 0, &cfg); + if (ret) { + dev_dbg(viif_dev->dev, "subdev: g_mbus_config error. %d\n", ret); + num_lane =3D viif_sd->num_lane; + } else { + switch (cfg.flags & V4L2_MBUS_CSI2_LANES) { + case V4L2_MBUS_CSI2_1_LANE: + num_lane =3D 1; + break; + case V4L2_MBUS_CSI2_2_LANE: + num_lane =3D 2; + break; + case V4L2_MBUS_CSI2_3_LANE: + num_lane =3D 3; + break; + case V4L2_MBUS_CSI2_4_LANE: + num_lane =3D 4; + break; + default: + num_lane =3D 4; + break; + } + } + + ret =3D v4l2_subdev_call(viif_sd->v4l2_sd, pad, get_fmt, 0, &fmt); + if (ret) + return -EINVAL; + + ret =3D viif_get_dv_timings(viif_dev, &timings); + if (ret) + return -EINVAL; + + dphy_rate =3D (timings.bt.pixelclock / 1000) * viif_get_mbus_bpp(fmt.form= at.code) / num_lane; + dphy_rate =3D dphy_rate / 1000; + + /* check error for CH0: all supported DTs */ + err_target.dt[0] =3D VISCONTI_CSI2_DT_RGB565; + err_target.dt[1] =3D VISCONTI_CSI2_DT_YUV4228B; + err_target.dt[2] =3D VISCONTI_CSI2_DT_YUV42210B; + err_target.dt[3] =3D VISCONTI_CSI2_DT_RGB888; + err_target.dt[4] =3D VISCONTI_CSI2_DT_RAW8; + err_target.dt[5] =3D VISCONTI_CSI2_DT_RAW10; + err_target.dt[6] =3D VISCONTI_CSI2_DT_RAW12; + err_target.dt[7] =3D VISCONTI_CSI2_DT_RAW14; + + /* Define errors to be masked */ + csi2rx_mask.mask[0] =3D 0x0000000F; /*check all for PHY_FATAL*/ + csi2rx_mask.mask[1] =3D 0x0001000F; /*check all for PKT_FATAL*/ + csi2rx_mask.mask[2] =3D 0x000F0F0F; /*check all for FRAME_FATAL*/ + csi2rx_mask.mask[3] =3D 0x000F000F; /*check all for PHY*/ + csi2rx_mask.mask[4] =3D 0x000F000F; /*check all for PKT*/ + csi2rx_mask.mask[5] =3D 0x00FF00FF; /*check all for LINE*/ + + return hwd_VIIF_csi2rx_initialize(viif_dev->ch, num_lane, HWD_VIIF_CSI2_D= PHY_L0L1L2L3, + dphy_rate, HWD_VIIF_ENABLE, &err_target, + HWD_VIIF_CSI2_INPUT_OWN, &csi2rx_mask); +} + +static int viif_csi2rx_start(struct viif_device *viif_dev) +{ + uint32_t vc_main =3D 0; + struct hwd_viif_csi2rx_packet packet =3D { 0 }; + + viif_dev->masked_gamma_path =3D 0U; + + return hwd_VIIF_csi2rx_start(viif_dev->ch, vc_main, HWD_VIIF_CSI2_NOT_CAP= TURE, &packet, + HWD_VIIF_DISABLE); +} + +static int viif_csi2rx_stop(struct viif_device *viif_dev) +{ + int32_t ret; + + ret =3D hwd_VIIF_csi2rx_stop(viif_dev->ch); + if (ret) + dev_err(viif_dev->dev, "csi2rx_stop error. %d\n", ret); + + hwd_VIIF_csi2rx_uninitialize(viif_dev->ch); + + return ret; +} + +/* ----- subdevice video operations ----- */ +static int visconti_viif_isp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + if (enable) { + int ret =3D viif_csi2rx_initialize(viif_dev); + if (ret) + return ret; + viif_csi2rx_start(viif_dev); + } else { + (void)viif_csi2rx_stop(viif_dev); + } + return 0; +} + +/* ----- subdevice pad operations ----- */ +static int visconti_viif_isp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad =3D=3D 0) { + /* sink */ + if (code->index > ARRAY_SIZE(visconti_mbus_formats) - 1) + return -EINVAL; + code->code =3D visconti_mbus_formats[code->index].code; + return 0; + } + + /* source */ + if (code->index > 0) + return -EINVAL; + code->code =3D MEDIA_BUS_FMT_RGB888_1X24; + return 0; +} + +static struct v4l2_mbus_framefmt *visconti_viif_isp_get_pad_fmt(struct v4l= 2_subdev *sd, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_subdev_state state =3D { + .pads =3D viif_dev->isp_subdev.pad_cfg, + }; + + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&viif_dev->isp_subdev.sd, sd_state, pa= d); + else + return v4l2_subdev_get_try_format(&viif_dev->isp_subdev.sd, &state, pad); +} + +static struct v4l2_rect *visconti_viif_isp_get_pad_crop(struct v4l2_subdev= *sd, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_subdev_state state =3D { + .pads =3D viif_dev->isp_subdev.pad_cfg, + }; + + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&viif_dev->isp_subdev.sd, sd_state, pad); + else + return v4l2_subdev_get_try_crop(&viif_dev->isp_subdev.sd, &state, pad); +} + +static struct v4l2_rect *visconti_viif_isp_get_pad_compose(struct v4l2_sub= dev *sd, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_subdev_state state =3D { + .pads =3D viif_dev->isp_subdev.pad_cfg, + }; + + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&viif_dev->isp_subdev.sd, sd_state, p= ad); + else + return v4l2_subdev_get_try_compose(&viif_dev->isp_subdev.sd, &state, pad= ); +} + +static int visconti_viif_isp_get_fmt(struct v4l2_subdev *sd, struct v4l2_s= ubdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + + mutex_lock(&viif_dev->isp_subdev.ops_lock); + fmt->format =3D *visconti_viif_isp_get_pad_fmt(sd, sd_state, fmt->pad, fm= t->which); + mutex_unlock(&viif_dev->isp_subdev.ops_lock); + + return 0; +} + +static void visconti_viif_isp_set_sink_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, u32 which) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + + pr_info("visconti_viif_isp_set_sink_fmt called %d", which); + + sink_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 0, which); + src_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 1, which); + + /* update mbus code only if it's available */ + if (viif_is_valid_mbus_code(format->code)) + sink_fmt->code =3D format->code; + + /* sink::mbus_code is derived from src::mbus_code */ + if (viif_get_mbus_rgb_out(sink_fmt->code)) + src_fmt->code =3D MEDIA_BUS_FMT_RGB888_1X24; + else + src_fmt->code =3D MEDIA_BUS_FMT_YUV8_1X24; + + /* size check */ + sink_fmt->width =3D format->width; + sink_fmt->height =3D format->height; + + *format =3D *sink_fmt; +} + +static void visconti_viif_isp_set_src_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, u32 which) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *src_crop; + + pr_info("visconti_viif_isp_set_src_fmt called %d", which); + + sink_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 0, V4L2_SUBDEV_F= ORMAT_ACTIVE); + src_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 1, which); + src_crop =3D visconti_viif_isp_get_pad_crop(sd, sd_state, 1, which); + + /* sink::mbus_code is derived from src::mbus_code */ + if (viif_get_mbus_rgb_out(sink_fmt->code)) + src_fmt->code =3D MEDIA_BUS_FMT_RGB888_1X24; + else + src_fmt->code =3D MEDIA_BUS_FMT_YUV8_1X24; + + /*size check*/ + src_fmt->width =3D format->width; + src_fmt->height =3D format->height; + + /*update crop*/ + src_crop->width =3D format->width; + src_crop->height =3D format->height; + + *format =3D *src_fmt; +} + +static int visconti_viif_isp_set_fmt(struct v4l2_subdev *sd, struct v4l2_s= ubdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + + mutex_lock(&viif_dev->isp_subdev.ops_lock); + + if (fmt->pad =3D=3D 0) + visconti_viif_isp_set_sink_fmt(sd, sd_state, &fmt->format, fmt->which); + else + visconti_viif_isp_set_src_fmt(sd, sd_state, &fmt->format, fmt->which); + + mutex_unlock(&viif_dev->isp_subdev.ops_lock); + + return 0; +} + +static int visconti_viif_isp_init_config(struct v4l2_subdev *sd, struct v4= l2_subdev_state *sd_state) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *src_crop, *sink_compose; + pr_info("visconti_viif_isp_init_config called"); + + sink_fmt =3D v4l2_subdev_get_try_format(&viif_dev->isp_subdev.sd, sd_stat= e, 0); + sink_fmt->width =3D 1920; + sink_fmt->height =3D 1080; + sink_fmt->field =3D V4L2_FIELD_NONE; + sink_fmt->code =3D MEDIA_BUS_FMT_SRGGB10_1X10; + + src_fmt =3D v4l2_subdev_get_try_format(&viif_dev->isp_subdev.sd, sd_state= , 1); + src_fmt->width =3D 1920; + src_fmt->height =3D 1080; + src_fmt->field =3D V4L2_FIELD_NONE; + src_fmt->code =3D MEDIA_BUS_FMT_YUV8_1X24; + + src_crop =3D v4l2_subdev_get_try_crop(&viif_dev->isp_subdev.sd, sd_state,= 1); + src_crop->top =3D 0; + src_crop->left =3D 0; + src_crop->width =3D 1920; + src_crop->height =3D 1080; + + sink_compose =3D v4l2_subdev_get_try_compose(&viif_dev->isp_subdev.sd, sd= _state, 0); + sink_compose->top =3D 0; + sink_compose->left =3D 0; + sink_compose->width =3D 1920; + sink_compose->height =3D 1080; + + return 0; +} + +static int visconti_viif_isp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_mbus_framefmt *sink_fmt; + int ret =3D -EINVAL; + + mutex_lock(&viif_dev->isp_subdev.ops_lock); + if (sel->pad =3D=3D 0) { + /* SINK PAD */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sink_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 0, sel->which); + sel->r.top =3D 0; + sel->r.left =3D 0; + sel->r.width =3D sink_fmt->width; + sel->r.height =3D sink_fmt->height; + ret =3D 0; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r =3D *visconti_viif_isp_get_pad_compose(sd, sd_state, 0, sel->whi= ch); + ret =3D 0; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* fixed value */ + sel->r.top =3D 0; + sel->r.left =3D 0; + sel->r.width =3D 8192; + sel->r.height =3D 4094; + ret =3D 0; + break; + } + } else { + /* SRC PAD */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r =3D *visconti_viif_isp_get_pad_crop(sd, sd_state, 1, sel->which); + ret =3D 0; + break; + } + } + mutex_unlock(&viif_dev->isp_subdev.ops_lock); + + return ret; +} + +static int visconti_viif_isp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct viif_device *viif_dev =3D ((struct isp_subdev *)sd)->viif_dev; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *rect; + int ret =3D -EINVAL; + + mutex_lock(&viif_dev->isp_subdev.ops_lock); + /* only source::selection::crop is writable */ + if (sel->pad =3D=3D 1) { + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + /* TODO: validation */ + rect =3D visconti_viif_isp_get_pad_crop(sd, sd_state, 1, sel->which); + *rect =3D sel->r; + sink_fmt =3D visconti_viif_isp_get_pad_fmt(sd, sd_state, 1, sel->which); + sink_fmt->width =3D sel->r.width; + sink_fmt->height =3D sel->r.height; + ret =3D 0; + break; + } + } + } + mutex_unlock(&viif_dev->isp_subdev.ops_lock); + + return ret; +} + +static const struct media_entity_operations visconti_viif_isp_media_ops = =3D { + .link_validate =3D v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_pad_ops visconti_viif_isp_pad_ops =3D { + .enum_mbus_code =3D visconti_viif_isp_enum_mbus_code, + .get_selection =3D visconti_viif_isp_get_selection, + .set_selection =3D visconti_viif_isp_set_selection, + .init_cfg =3D visconti_viif_isp_init_config, + .get_fmt =3D visconti_viif_isp_get_fmt, + .set_fmt =3D visconti_viif_isp_set_fmt, + .link_validate =3D v4l2_subdev_link_validate_default, +}; + +static const struct v4l2_subdev_video_ops visconti_viif_isp_video_ops =3D { + .s_stream =3D visconti_viif_isp_s_stream, +}; + +static const struct v4l2_subdev_ops visconti_viif_isp_ops =3D { + .video =3D &visconti_viif_isp_video_ops, + .pad =3D &visconti_viif_isp_pad_ops, +}; + +/* ----- control handler ----- */ +#define V4L2_CID_VISCONTI_VIIF_ISP_BASE (V4L2_CID_USER_BASE + 0x1000) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_INPUT_MODE (V4L2_CID_VISCONTI_VI= IF_ISP_BASE + 3) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_BLACK_LEVEL_CORRECTION = \ + (V4L2_CID_VISCONTI_VIIF_ISP_BASE + 4) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_MAIN_PROCESS (V4L2_CID_VISCONTI_= VIIF_ISP_BASE + 5) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_AWB (V4L2_CID_VISCONTI_VI= IF_ISP_BASE + 6) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_HDRC (V4L2_CID_VISCONTI_V= IIF_ISP_BASE + 7) +#define V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_IMG_QUALITY_ADJUSTMENT = \ + (V4L2_CID_VISCONTI_VIIF_ISP_BASE + 8) +#define V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_CALIBRATION_STATUS = \ + (V4L2_CID_VISCONTI_VIIF_ISP_BASE + 9) +#define V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_ERR_STATUS (V4L2_CID_VISCONT= I_VIIF_ISP_BASE + 10) +#define V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_UNDIST (V4L2_CID_VISCONTI_VIIF_= ISP_BASE + 11) +#define V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_ROI (V4L2_CID_VISCONTI_VIIF_IS= P_BASE + 12) +#define V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_CROP (V4L2_CID_VISCONTI_VIIF_I= SP_BASE + 13) +#define COMPOUND_TYPE_SAMPLE01 0x0280 + +int viif_l1_set_input_mode(struct viif_device *viif_dev, + struct viif_l1_input_mode_config *input_mode); +int viif_l1_set_black_level_correction(struct viif_device *viif_dev, + struct viif_l1_black_level_correction_config *blc); +int viif_l1_set_main_process(struct viif_device *viif_dev, + struct viif_l1_main_process_config *mpro); +int viif_l1_set_awb(struct viif_device *viif_dev, struct viif_l1_awb_confi= g *l1_awb); +int viif_l1_set_hdrc(struct viif_device *viif_dev, struct viif_l1_hdrc_con= fig *hdrc); +int viif_l1_set_img_quality_adjustment(struct viif_device *viif_dev, + struct viif_l1_img_quality_adjustment_config *img_quality); +int viif_csi2rx_get_calibration_status( + struct viif_device *viif_dev, + struct viif_csi2rx_dphy_calibration_status *calibration_status); +int viif_csi2rx_get_err_status(struct viif_device *viif_dev, + struct viif_csi2rx_err_status *csi_err); +int viif_l2_set_undist(struct viif_device *viif_dev, struct viif_l2_undist= _config *undist); +int viif_l2_set_roi(struct viif_device *viif_dev, struct viif_l2_roi_confi= g *roi); +int viif_l2_set_crop(struct viif_device *viif_dev, struct viif_l2_crop_con= fig *l2_crop); + +static int viif_l2_set_roi_wrap(struct viif_device *viif_dev, struct viif_= l2_roi_config *roi) +{ + int ret; + + ret =3D viif_l2_set_roi(viif_dev, roi); + if (!ret) { + struct v4l2_rect *rect; + rect =3D visconti_viif_isp_get_pad_compose(&viif_dev->isp_subdev.sd, NUL= L, 0, + V4L2_SUBDEV_FORMAT_ACTIVE); + rect->top =3D 0; + rect->left =3D 0; + rect->width =3D roi->corrected_hsize; + rect->height =3D roi->corrected_vsize; + } + + return ret; +} + +static int visconti_viif_isp_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct viif_device *viif_dev =3D ctrl->priv; + + pr_info("isp_set_ctrl: %s", ctrl->name); + if (!viif_dev->is_powered) { + pr_info("warning: visconti viif HW is not powered"); + return 0; + } + + switch (ctrl->id) { + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_INPUT_MODE: + return viif_l1_set_input_mode(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_BLACK_LEVEL_CORRECTION: + return viif_l1_set_black_level_correction(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_MAIN_PROCESS: + return viif_l1_set_main_process(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_AWB: + return viif_l1_set_awb(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_HDRC: + return viif_l1_set_hdrc(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_IMG_QUALITY_ADJUSTMENT: + return viif_l1_set_img_quality_adjustment(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_UNDIST: + return viif_l2_set_undist(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_ROI: + return viif_l2_set_roi_wrap(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_CROP: + return viif_l2_set_crop(viif_dev, ctrl->p_new.p); + default: + pr_info("unknown_ctrl: id=3D%08X val=3D%d", ctrl->id, ctrl->val); + break; + } + return 0; +} + +static int visconti_viif_isp_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct viif_device *viif_dev =3D ctrl->priv; + + pr_info("isp_get_ctrl: %s", ctrl->name); + if (!viif_dev->is_powered) { + pr_info("warning: visconti viif HW is not powered"); + return 0; + } + + switch (ctrl->id) { + case V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_CALIBRATION_STATUS: + return viif_csi2rx_get_calibration_status(viif_dev, ctrl->p_new.p); + case V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_ERR_STATUS: + return viif_csi2rx_get_err_status(viif_dev, ctrl->p_new.p); + default: + pr_info("unknown_ctrl: id=3D%08X val=3D%d", ctrl->id, ctrl->val); + break; + } + return 0; +} + +static const struct v4l2_ctrl_ops visconti_viif_isp_ctrl_ops =3D { + .g_volatile_ctrl =3D visconti_viif_isp_get_ctrl, + .s_ctrl =3D visconti_viif_isp_set_ctrl, +}; + +static bool visconti_viif_isp_custom_ctrl_equal(const struct v4l2_ctrl *ct= rl, u32 idx, + union v4l2_ctrl_ptr ptr1, union v4l2_ctrl_ptr ptr2) +{ + return !memcmp(ptr1.p_const, ptr2.p_const, ctrl->elem_size); +} + +static void visconti_viif_isp_custom_ctrl_init(const struct v4l2_ctrl *ctr= l, u32 idx, + union v4l2_ctrl_ptr ptr) +{ + if (ctrl->p_def.p_const) + memcpy(ptr.p, ctrl->p_def.p_const, ctrl->elem_size); + else + memset(ptr.p, 0, ctrl->elem_size); +} + +static void visconti_viif_isp_custom_ctrl_log(const struct v4l2_ctrl *ctrl) +{ + pr_cont("viif specific: %s", ctrl->name); + return; +} + +static int visconti_viif_isp_custom_ctrl_validate(const struct v4l2_ctrl *= ctrl, u32 idx, + union v4l2_ctrl_ptr ptr) +{ + pr_info("std_validate: %s", ctrl->name); + return 0; +} + +static const struct v4l2_ctrl_type_ops custom_type_ops =3D { + .equal =3D visconti_viif_isp_custom_ctrl_equal, + .init =3D visconti_viif_isp_custom_ctrl_init, + .log =3D visconti_viif_isp_custom_ctrl_log, + .validate =3D visconti_viif_isp_custom_ctrl_validate, +}; + +#define CTRL_CONFIG_DEFAULT_ENTRY = \ + .ops =3D &visconti_viif_isp_ctrl_ops, .type_ops =3D &custom_type_ops, = \ + .type =3D COMPOUND_TYPE_SAMPLE01, .flags =3D V4L2_CTRL_FLAG_EXECUTE_ON_WR= ITE + +#define CTRL_CONFIG_RDONLY_ENTRY = \ + .ops =3D &visconti_viif_isp_ctrl_ops, .type_ops =3D &custom_type_ops, = \ + .type =3D COMPOUND_TYPE_SAMPLE01, .flags =3D V4L2_CTRL_FLAG_VOLATILE + +static const struct v4l2_ctrl_config visconti_viif_isp_ctrl_config[] =3D { + /* L1_SET_INPUT_MODE */ { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_INPUT_MODE, + .name =3D "l1_input_mode", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_input_mode_config), + }, + /* L1_SET_BLACK_LEVEL_CORRECTION */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_BLACK_LEVEL_CORRECTION, + .name =3D "l1_black_level_correction", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_black_level_correction_config), + }, + /* L1_SET_MAIN_PROCESS */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_MAIN_PROCESS, + .name =3D "l1_main_process", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_main_process_config), + }, + /* L1_SET_AWB */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_AWB, + .name =3D "l1_awb", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_awb_config), + }, + /* L1_SET_HDRC */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_HDRC, + .name =3D "l1_hdrc", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_hdrc_config), + }, + /* L1_SET_IMG_QUALITY_ADJUSTMENT */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L1_SET_IMG_QUALITY_ADJUSTMENT, + .name =3D "l1_img_quality_adjustment", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l1_img_quality_adjustment_config), + }, + /* CSI2RX_GET_CALIBRATION_STATUS */ + { + CTRL_CONFIG_RDONLY_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_CALIBRATION_STATUS, + .name =3D "csi2rx_calibration_status", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_csi2rx_dphy_calibration_status), + }, + /* CSI2RX_GET_ERR_STATUS */ + { + CTRL_CONFIG_RDONLY_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_CSI2RX_GET_ERR_STATUS, + .name =3D "csi2rx_err_status", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_csi2rx_err_status), + }, + /* L2_SET_UNDIST */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_UNDIST, + .name =3D "l2_undist", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l2_undist_config), + }, + /* L2_SET_ROI */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_ROI, + .name =3D "l2_roi", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l2_roi_config), + }, + /* L2_SET_CROP */ + { + CTRL_CONFIG_DEFAULT_ENTRY, + .id =3D V4L2_CID_VISCONTI_VIIF_ISP_L2_SET_CROP, + .name =3D "l2_crop", + .p_def =3D { .p_const =3D NULL }, + .elem_size =3D sizeof(struct viif_l2_crop_config), + }, + +}; + +static int visconti_viif_isp_init_controls(struct viif_device *viif_dev) +{ + struct v4l2_ctrl_handler *ctrl_handler =3D &viif_dev->isp_subdev.ctrl_han= dler; + int ret; + int i; + + ret =3D v4l2_ctrl_handler_init(ctrl_handler, 10); + if (ret) { + dev_err(viif_dev->dev, "failed on v4l2_ctrl_handler_init"); + return ret; + } + + for (i =3D 0; i < ARRAY_SIZE(visconti_viif_isp_ctrl_config); i++) { + struct v4l2_ctrl *ctrl; + + ctrl =3D v4l2_ctrl_new_custom(ctrl_handler, &visconti_viif_isp_ctrl_conf= ig[i], + viif_dev); + if (ctrl =3D=3D NULL) { + dev_err(viif_dev->dev, "failed to add ctrl crop: %d", ctrl_handler->err= or); + return ctrl_handler->error; + } + } + + viif_dev->isp_subdev.sd.ctrl_handler =3D &viif_dev->isp_subdev.ctrl_handl= er; + return 0; +} + +/* ----- register/remove isp subdevice node ----- */ +int visconti_viif_isp_register(struct viif_device *viif_dev) +{ + struct v4l2_subdev_state state =3D { + .pads =3D viif_dev->isp_subdev.pad_cfg, + }; + struct media_pad *pads =3D viif_dev->isp_subdev.pads; + struct v4l2_subdev *sd =3D &viif_dev->isp_subdev.sd; + int ret; + + viif_dev->isp_subdev.viif_dev =3D viif_dev; + + v4l2_subdev_init(sd, &visconti_viif_isp_ops); + sd->flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.ops =3D &visconti_viif_isp_media_ops; + sd->entity.function =3D MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->owner =3D THIS_MODULE; + strscpy(sd->name, "visconti-viif:isp", sizeof(sd->name)); + + pads[0].flags =3D MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + pads[1].flags =3D MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT; + + mutex_init(&viif_dev->isp_subdev.ops_lock); + + visconti_viif_isp_init_controls(viif_dev); + + ret =3D media_entity_pads_init(&sd->entity, 2, pads); + if (ret) { + dev_err(viif_dev->dev, "Failed on media_entity_pads_init\n"); + return ret; + } + + ret =3D v4l2_device_register_subdev(&viif_dev->v4l2_dev, sd); + if (ret) { + dev_err(viif_dev->dev, "Failed to resize ISP subdev\n"); + goto err_cleanup_media_entity; + } + + visconti_viif_isp_init_config(sd, &state); + + return 0; + +err_cleanup_media_entity: + media_entity_cleanup(&sd->entity); + return ret; +} + +void visconti_viif_isp_unregister(struct viif_device *viif_dev) +{ + v4l2_device_unregister_subdev(&viif_dev->isp_subdev.sd); + media_entity_cleanup(&viif_dev->isp_subdev.sd.entity); +} --=20 2.17.1