@@ -1972,6 +1972,7 @@ L: linux-media@vger.kernel.org
S: Maintained
T: git git://linuxtv.org/media_tree.git
F: Documentation/devicetree/bindings/media/intel,keembay-camera.yaml
+F: drivers/media/platform/keembay-camera/
ARM/IP FABRICS DOUBLE ESPRESSO MACHINE SUPPORT
M: Lennert Buytenhek <kernel@wantstofly.org>
@@ -171,6 +171,7 @@ source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
source "drivers/media/platform/sunxi/Kconfig"
+source "drivers/media/platform/keembay-camera/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
@@ -84,3 +84,5 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
obj-y += sunxi/
obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
+
+obj-$(CONFIG_VIDEO_INTEL_KEEMBAY_CAMERA) += keembay-camera/
new file mode 100644
@@ -0,0 +1,11 @@
+config VIDEO_INTEL_KEEMBAY_CAMERA
+ tristate "Intel Keem Bay camera subsystem"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ depends on XLINK_CORE && HAS_DMA && OF
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_FWNODE
+ help
+ This is a V4L2 driver for the Intel Keem Bay VPU camera interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called keembay_cam
new file mode 100644
@@ -0,0 +1,3 @@
+keembay-cam-objs = keembay-camera.o keembay-cam-xlink.o keembay-isp.o
+
+obj-$(CONFIG_VIDEO_INTEL_KEEMBAY_CAMERA) += keembay-cam.o
new file mode 100644
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Keem Bay camera xlink
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#include <linux/device.h>
+#include <linux/idr.h>
+
+#include "keembay-cam-xlink.h"
+
+/**
+ * kmb_cam_xlink_init - Initialize xlink for VPU camera communication
+ * @xlink_cam: Pointer to xlink camera handle
+ * @dev: Client device of the xlink
+ *
+ * Perform initialization and establish connection with xlink VPUIP device
+ *
+ * Return: 0 if successful, error code otherwise
+ */
+int kmb_cam_xlink_init(struct kmb_xlink_cam *xlink_cam, struct device *dev)
+{
+ int ret;
+
+ /* Connect to the device before opening channels */
+ memset(&xlink_cam->handle, 0, sizeof(xlink_cam->handle));
+ xlink_cam->handle.dev_type = VPUIP_DEVICE;
+ ret = xlink_connect(&xlink_cam->handle);
+ if (ret) {
+ dev_err(xlink_cam->dev, "Failed to connect: %d\n", ret);
+ return ret;
+ }
+
+ ida_init(&xlink_cam->channel_ids);
+ xlink_cam->ctrl_chan_refcnt = 0;
+
+ mutex_init(&xlink_cam->lock);
+ xlink_cam->dev = dev;
+
+ return 0;
+}
+
+/**
+ * kmb_cam_xlink_cleanup - Cleanup xlink camera communication
+ * @xlink_cam: Pointer to xlink camera handle
+ *
+ * Return: 0 if successful, error code otherwise
+ */
+void kmb_cam_xlink_cleanup(struct kmb_xlink_cam *xlink_cam)
+{
+ /* Disconnect from the device after closing channels */
+ xlink_disconnect(&xlink_cam->handle);
+ ida_destroy(&xlink_cam->channel_ids);
+}
new file mode 100644
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Keem Bay camera xlink
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#ifndef KEEMBAY_CAM_XLINK_H
+#define KEEMBAY_CAM_XLINK_H
+
+#include <linux/xlink.h>
+
+/**
+ * struct kmb_xlink_cam - KMB Camera xlink communication
+ * @dev: Device client of the xlink
+ * @lock: Mutex to serialize access to kmb xlink communication channels
+ * @handle: Xlink handle
+ * @ctrl_chan_refcnt: Main control channel reference count
+ * @channel_ids: Channel IDs. Each channel should have unique ID
+ */
+struct kmb_xlink_cam {
+ struct device *dev;
+ struct mutex lock;
+ struct xlink_handle handle;
+ unsigned int ctrl_chan_refcnt;
+ struct ida channel_ids;
+};
+
+int kmb_cam_xlink_init(struct kmb_xlink_cam *xlink_cam, struct device *dev);
+void kmb_cam_xlink_cleanup(struct kmb_xlink_cam *xlink_cam);
+
+#endif /* KEEMBAY_CAM_XLINK_H */
new file mode 100644
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Keem Bay camera driver.
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+
+#include <media/v4l2-fwnode.h>
+
+#include "keembay-camera.h"
+
+#define KMB_CAM_NUM_PORTS 6
+
+/* RX-CTRL to data lanes mapping
+ * 2-lanes
+ * RX-CTRL#0 - {1, 2}
+ * RX-CTRL#1 - {4, 5}
+ * RX-CTRL#2 - {7, 8}
+ * RX-CTRL#3 - {10, 11}
+ * RX-CTRL#4 - {13, 14}
+ * RX-CTRL#5 - {16, 17}
+ * 4-lanes
+ * RX-CTRL#0 - {1, 2, 4, 5}
+ * RX-CTRL#2 - {7, 8, 10, 11}
+ * RX-CTRL#4 - {13, 14, 16, 17}
+ */
+static const u8 rx_ctrl[KMB_CAM_NUM_PORTS][2][4] = {
+ { { 1, 2 }, { 1, 2, 4, 5 } },
+ { { 4, 5 }, {} },
+ { { 7, 8 }, { 7, 8, 10, 11 } },
+ { { 10, 11 }, {} },
+ { { 13, 14 }, { 13, 14, 16, 17 } },
+ { { 16, 17 }, {} }
+};
+
+static int get_rx_id(const u8 data_lanes[],
+ const unsigned short num_data_lanes)
+{
+ unsigned int i, j;
+
+ for (i = 0; i < KMB_CAM_NUM_PORTS; i++) {
+ for (j = 0; j < ARRAY_SIZE(rx_ctrl[i]); j++) {
+ if (!memcmp(data_lanes, rx_ctrl[i][j],
+ num_data_lanes * sizeof(u8)))
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int kmb_cam_bound(struct v4l2_async_notifier *n,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct v4l2_device *v4l2_dev = n->v4l2_dev;
+ struct kmb_camera *kmb_cam =
+ container_of(v4l2_dev, struct kmb_camera, v4l2_dev);
+ struct kmb_camera_receiver *receiver =
+ container_of(asd, struct kmb_camera_receiver, asd);
+ int ret;
+
+ ret = kmb_isp_init(&receiver->isp, kmb_cam->dev,
+ &receiver->csi2_config, &kmb_cam->xlink_cam);
+ if (ret < 0)
+ return ret;
+
+ ret = kmb_isp_register_entities(&receiver->isp, &kmb_cam->v4l2_dev);
+ if (ret < 0)
+ goto error_isp_cleanup;
+
+ ret = media_create_pad_link(&sd->entity, 0,
+ &receiver->isp.subdev.entity,
+ KMB_ISP_SINK_PAD_SENSOR,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev, "Fail to link %s->%s entities",
+ sd->entity.name, receiver->isp.subdev.entity.name);
+ goto error_unregister_entities;
+ }
+
+ return 0;
+
+error_unregister_entities:
+ kmb_isp_unregister_entities(&receiver->isp);
+error_isp_cleanup:
+ kmb_isp_cleanup(&receiver->isp);
+
+ return ret;
+}
+
+static int kmb_cam_complete(struct v4l2_async_notifier *n)
+{
+ return v4l2_device_register_subdev_nodes(n->v4l2_dev);
+}
+
+static void kmb_cam_unbind(struct v4l2_async_notifier *n,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct kmb_camera_receiver *receiver =
+ container_of(asd, struct kmb_camera_receiver, asd);
+
+ kmb_isp_unregister_entities(&receiver->isp);
+ kmb_isp_cleanup(&receiver->isp);
+}
+
+static const struct v4l2_async_notifier_operations notifier_ops = {
+ .bound = kmb_cam_bound,
+ .complete = kmb_cam_complete,
+ .unbind = kmb_cam_unbind
+};
+
+static int kmb_cam_parse_nodes(struct kmb_camera *kmb_cam,
+ struct v4l2_async_notifier *n)
+{
+ struct fwnode_handle *fwnode = NULL;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < KMB_CAM_NUM_PORTS; i++) {
+ struct v4l2_fwnode_endpoint ep_data = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY,
+ };
+ struct kmb_camera_receiver *receiver;
+ int rx_id;
+
+ fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(kmb_cam->dev),
+ i, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!fwnode)
+ continue;
+
+ ret = v4l2_fwnode_endpoint_parse(fwnode, &ep_data);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev,
+ "No endpoint to parse in this fwnode");
+ goto error_fwnode_handle_put;
+ }
+
+ rx_id = get_rx_id(ep_data.bus.mipi_csi2.data_lanes,
+ ep_data.bus.mipi_csi2.num_data_lanes);
+ if (rx_id < 0) {
+ dev_err(kmb_cam->dev, "Invalid RX ID");
+ ret = rx_id;
+ goto error_fwnode_handle_put;
+ }
+
+ receiver =
+ v4l2_async_notifier_add_fwnode_remote_subdev(&kmb_cam->v4l2_notifier,
+ fwnode,
+ struct kmb_camera_receiver);
+ if (IS_ERR(receiver)) {
+ ret = PTR_ERR(receiver);
+ goto error_fwnode_handle_put;
+ }
+
+ receiver->csi2_config.rx_id = rx_id;
+ receiver->csi2_config.num_lanes =
+ ep_data.bus.mipi_csi2.num_data_lanes;
+
+ fwnode_handle_put(fwnode);
+ }
+
+ return 0;
+
+error_fwnode_handle_put:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int kmb_cam_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct v4l2_device *v4l2_dev;
+ struct kmb_camera *kmb_cam;
+ int ret;
+
+ kmb_cam = devm_kzalloc(dev, sizeof(*kmb_cam), GFP_KERNEL);
+ if (!kmb_cam)
+ return -ENOMEM;
+
+ kmb_cam->dev = dev;
+
+ platform_set_drvdata(pdev, kmb_cam);
+
+ ret = kmb_cam_xlink_init(&kmb_cam->xlink_cam, dev);
+ if (ret < 0)
+ return ret;
+
+ strscpy(kmb_cam->media_dev.model, "Keem Bay camera",
+ sizeof(kmb_cam->media_dev.model));
+ kmb_cam->media_dev.dev = dev;
+ kmb_cam->media_dev.hw_revision = 0;
+ media_device_init(&kmb_cam->media_dev);
+
+ v4l2_dev = &kmb_cam->v4l2_dev;
+ v4l2_dev->mdev = &kmb_cam->media_dev;
+ strscpy(v4l2_dev->name, "keembay-camera", sizeof(v4l2_dev->name));
+
+ ret = v4l2_device_register(dev, &kmb_cam->v4l2_dev);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev, "Fail to register v4l2_device: %d", ret);
+ goto error_xlink_cleanup;
+ }
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ dev_info(dev, "Default CMA memory region will be used!");
+
+ v4l2_async_notifier_init(&kmb_cam->v4l2_notifier);
+ ret = kmb_cam_parse_nodes(kmb_cam, &kmb_cam->v4l2_notifier);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev, "Fail to parse nodes: %d", ret);
+ goto error_async_notifier_cleanup;
+ }
+
+ kmb_cam->v4l2_notifier.ops = ¬ifier_ops;
+ ret = v4l2_async_notifier_register(&kmb_cam->v4l2_dev,
+ &kmb_cam->v4l2_notifier);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev, "Could not register notifier! %d", ret);
+ goto error_async_notifier_cleanup;
+ }
+
+ ret = media_device_register(&kmb_cam->media_dev);
+ if (ret < 0) {
+ dev_err(kmb_cam->dev, "Fail to register media device %d", ret);
+ goto error_async_notifier_unregister;
+ }
+
+ return 0;
+
+error_async_notifier_unregister:
+ v4l2_async_notifier_unregister(&kmb_cam->v4l2_notifier);
+error_async_notifier_cleanup:
+ v4l2_async_notifier_cleanup(&kmb_cam->v4l2_notifier);
+ v4l2_device_unregister(&kmb_cam->v4l2_dev);
+error_xlink_cleanup:
+ kmb_cam_xlink_cleanup(&kmb_cam->xlink_cam);
+
+ return ret;
+}
+
+static int kmb_cam_remove(struct platform_device *pdev)
+{
+ struct kmb_camera *kmb_cam = platform_get_drvdata(pdev);
+
+ v4l2_async_notifier_unregister(&kmb_cam->v4l2_notifier);
+ v4l2_async_notifier_cleanup(&kmb_cam->v4l2_notifier);
+
+ media_device_unregister(&kmb_cam->media_dev);
+ media_device_cleanup(&kmb_cam->media_dev);
+ v4l2_device_unregister(&kmb_cam->v4l2_dev);
+
+ kmb_cam_xlink_cleanup(&kmb_cam->xlink_cam);
+
+ return 0;
+}
+
+static const struct of_device_id kmb_cam_dt_match[] = {
+ {.compatible = "intel,keembay-camera"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, kmb_cam_dt_match);
+
+static struct platform_driver keembay_camera_driver = {
+ .probe = kmb_cam_probe,
+ .remove = kmb_cam_remove,
+ .driver = {
+ .name = "keembay-camera",
+ .owner = THIS_MODULE,
+ .of_match_table = kmb_cam_dt_match,
+ }
+};
+
+module_platform_driver(keembay_camera_driver);
+
+MODULE_DESCRIPTION("Intel Keem Bay camera");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Keem Bay camera driver.
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#ifndef KEEMBAY_CAMERA_H
+#define KEEMBAY_CAMERA_H
+
+#include <media/v4l2-device.h>
+
+#include "keembay-cam-xlink.h"
+#include "keembay-isp.h"
+
+/**
+ * struct kmb_camera_receiver - Keem Bay camera receiver
+ * @asd: V4L2 asynchronous sub-device
+ * @csi2_config: CSI-2 configuration
+ * @isp: ISP device
+ */
+struct kmb_camera_receiver {
+ struct v4l2_async_subdev asd;
+ struct kmb_isp_csi2_config csi2_config;
+ struct kmb_isp isp;
+};
+
+/**
+ * struct kmb_cam - Keem Bay camera media device
+ * @dev: Pointer to basic device structure
+ * @media_dev: Media device
+ * @v4l2_dev: V4L2 device
+ * @v4l2_notifier: V4L2 async notifier
+ * @xlink_cam: Xlink camera communication handler
+ */
+struct kmb_camera {
+ struct device *dev;
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_async_notifier v4l2_notifier;
+ struct kmb_xlink_cam xlink_cam;
+};
+
+#endif /* KEEMBAY_CAMERA_H */
new file mode 100644
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Keem Bay camera ISP driver.
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#include "keembay-isp.h"
+
+/**
+ * kmb_isp_init - Initialize Kmb isp subdevice
+ * @kmb_isp: Pointer to kmb isp device
+ * @dev: Pointer to camera device for which isp will be associated with
+ * @csi2_config: Csi2 configuration
+ * @xlink_cam: Xlink camera communication handle
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+int kmb_isp_init(struct kmb_isp *kmb_isp, struct device *dev,
+ struct kmb_isp_csi2_config *csi2_config,
+ struct kmb_xlink_cam *xlink_cam)
+{
+ return 0;
+}
+
+/**
+ * kmb_isp_cleanup - Cleanup kmb isp sub-device resourcess allocated in init
+ * @kmb_isp: Pointer to kmb isp sub-device
+ */
+void kmb_isp_cleanup(struct kmb_isp *kmb_isp)
+{ }
+
+/**
+ * kmb_isp_register_entities - Register entities
+ * @kmb_isp: pointer to kmb isp device
+ * @v4l2_dev: pointer to V4L2 device drivers
+ *
+ * Register all entities in the pipeline and create
+ * links between them.
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+int kmb_isp_register_entities(struct kmb_isp *kmb_isp,
+ struct v4l2_device *v4l2_dev)
+{
+ return 0;
+}
+
+/**
+ * kmb_isp_unregister_entities - Unregister this media's entities
+ * @kmb_isp: pointer to kmb isp device
+ */
+void kmb_isp_unregister_entities(struct kmb_isp *kmb_isp)
+{ }
new file mode 100644
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Keem Bay camera ISP driver.
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#ifndef KEEMBAY_ISP_H
+#define KEEMBAY_ISP_H
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+#define KMB_ISP_DRV_NAME "keembay-camera-isp"
+
+#define KMB_ISP_SINK_PAD_SENSOR 0
+#define KMB_ISP_SINK_PAD_CFG 1
+#define KMB_ISP_SRC_PAD_VID 2
+#define KMB_ISP_PADS_NUM 3
+
+/**
+ * struct kmb_isp_csi2_config - Isp csi2 configuration
+ * @rx_id: Source port id
+ * @num_lanes: Number of physical lanes
+ */
+struct kmb_isp_csi2_config {
+ u32 rx_id;
+ u32 num_lanes;
+};
+
+/**
+ * struct kmb_isp - Keem Bay camera ISP device structure
+ * @dev: Pointer to basic device structure
+ * @lock: Mutex serilizing access to ISP device
+ * @thread: Pointer to worker thread data
+ * @xlink_cam: Xlink camera communication handler
+ * @msg_phy_addr: ISP channel physical CMA address
+ * @msg_vaddr: ISP channel virtual CMA address
+ * @cfg_q_lock: Mutex to serialize access to isp cfg bufferss queue
+ * @isp_cfgs_queue: Isp cfg buffers queue
+ * @isp_streaming: Flag to indicate ISP state
+ * @source_streaming: Flag to indicate source state
+ * @source_stopped: Completion to wait until VPU source is stopped
+ * @subdev: V4L2 sub-device
+ * @pads: Array of supported isp pads
+ * @active_pad_fmt: Array holding active pad formats
+ * @try_pad_fmt: Array holding try pad formats
+ * @csi2_config: CSI2 configuration
+ * @source_fmt: Pointer to isp source format
+ * @sequence: frame sequence number
+ */
+struct kmb_isp {
+ struct device *dev;
+ struct mutex lock;
+ struct task_struct *thread;
+
+ struct kmb_xlink_cam *xlink_cam;
+
+ dma_addr_t msg_phy_addr;
+ void *msg_vaddr;
+
+ struct mutex cfg_q_lock;
+ struct list_head isp_cfgs_queue;
+
+ bool isp_streaming;
+ bool source_streaming;
+ struct completion source_stopped;
+
+ struct v4l2_subdev subdev;
+ struct media_pad pads[KMB_ISP_PADS_NUM];
+
+ struct v4l2_subdev_format active_pad_fmt[KMB_ISP_PADS_NUM];
+
+ struct v4l2_subdev_format try_pad_fmt[KMB_ISP_PADS_NUM];
+
+ struct kmb_isp_csi2_config csi2_config;
+ const struct kmb_isp_source_format *source_fmt;
+
+ u32 sequence;
+};
+
+int kmb_isp_init(struct kmb_isp *kmb_isp, struct device *dev,
+ struct kmb_isp_csi2_config *csi2_config,
+ struct kmb_xlink_cam *xlink_cam);
+void kmb_isp_cleanup(struct kmb_isp *kmb_isp);
+
+int kmb_isp_register_entities(struct kmb_isp *kmb_isp,
+ struct v4l2_device *v4l2_dev);
+void kmb_isp_unregister_entities(struct kmb_isp *kmb_isp);
+
+#endif /* KEEMBAY_ISP_H */