#include <drm/drmP.h>
#include <linux/component.h>
#include <linux/device.h>
+#include <linux/list.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <video/videomode.h>
+#include "xilinx_drm_sdi.h"
#include "xilinx_vtc.h"
/* SDI register offsets */
#define ST352_BYTE4 0x01
#define INVALID_VALUE -1
#define GT_TIMEOUT 500
+
+static LIST_HEAD(xilinx_sdi_list);
+static DEFINE_MUTEX(xilinx_sdi_lock);
/**
* enum payload_line_1 - Payload Ids Line 1 number
* @PAYLD_LN1_HD_3_6_12G: line 1 HD,3G,6G or 12G mode value
* @mode_flags: SDI operation mode related flags
* @wait_event: wait event
* @event_received: wait event status
+ * @list: entry in the global SDI subsystem list
+ * @vblank_fn: vblank handler
+ * @vblank_data: vblank data to be used in vblank_fn
* @sdi_mode: configurable SDI mode parameter, supported values are:
* 0 - HD
* 1 - SD
u32 mode_flags;
wait_queue_head_t wait_event;
bool event_received;
+ struct list_head list;
+ void (*vblank_fn)(void *);
+ void *vblank_data;
struct drm_property *sdi_mode;
u32 sdi_mod_prop_val;
struct drm_property *sdi_data_strm;
.unbind = xilinx_sdi_unbind,
};
+static irqreturn_t xilinx_sdi_vblank_handler(int irq, void *data)
+{
+ struct xilinx_sdi *sdi = (struct xilinx_sdi *)data;
+ u32 intr = xilinx_vtc_intr_get(sdi->vtc);
+
+ if (!intr)
+ return IRQ_NONE;
+
+ if (sdi->vblank_fn)
+ sdi->vblank_fn(sdi->vblank_data);
+
+ xilinx_vtc_intr_clear(sdi->vtc, intr);
+ return IRQ_HANDLED;
+}
+
+/**
+ * xilinx_drm_sdi_enable_vblank - Enable the vblank handling
+ * @sdi: SDI subsystem
+ * @vblank_fn: callback to be called on vblank event
+ * @vblank_data: data to be used in @vblank_fn
+ *
+ * This function register the vblank handler, and the handler will be triggered
+ * on vblank event after.
+ */
+void xilinx_drm_sdi_enable_vblank(struct xilinx_sdi *sdi,
+ void (*vblank_fn)(void *),
+ void *vblank_data)
+{
+ sdi->vblank_fn = vblank_fn;
+ sdi->vblank_data = vblank_data;
+ xilinx_vtc_vblank_enable(sdi->vtc);
+}
+EXPORT_SYMBOL_GPL(xilinx_drm_sdi_enable_vblank);
+
+/**
+ * xilinx_drm_sdi_disable_vblank - Disable the vblank handling
+ * @sdi: SDI subsystem
+ *
+ * Disable the vblank handler. The vblank handler and data are unregistered.
+ */
+void xilinx_drm_sdi_disable_vblank(struct xilinx_sdi *sdi)
+{
+ sdi->vblank_fn = NULL;
+ sdi->vblank_data = NULL;
+ xilinx_vtc_vblank_disable(sdi->vtc);
+}
+
+/**
+ * xilinx_sdi_register_device - Register the SDI subsystem to the global list
+ * @sdi: SDI subsystem
+ *
+ * Register the SDI subsystem instance to the global list
+ */
+static void xilinx_sdi_register_device(struct xilinx_sdi *sdi)
+{
+ mutex_lock(&xilinx_sdi_lock);
+ list_add_tail(&sdi->list, &xilinx_sdi_list);
+ mutex_unlock(&xilinx_sdi_lock);
+}
+
+/**
+ * xilinx_drm_sdi_of_get - Get the SDI subsystem instance
+ * @np: parent device node
+ *
+ * This function searches and returns a SDI subsystem structure for
+ * the parent device node, @np. The SDI subsystem node should be a child node
+ * of @np, with 'xlnx,v-smpte-uhdsdi-tx-ss' property pointing to the SDI
+ * device node. An instance can be shared by multiple users.
+ *
+ * Return: corresponding SDI subsystem structure if found. NULL if
+ * the device node doesn't have 'xlnx,v-smpte-uhdsdi-tx-ss' property, or
+ * -EPROBE_DEFER error pointer if the the SDI subsystem isn't found.
+ */
+struct xilinx_sdi *xilinx_drm_sdi_of_get(struct device_node *np)
+{
+ struct xilinx_sdi *found = NULL;
+ struct xilinx_sdi *sdi;
+ struct device_node *ep_node, *sdi_node;
+
+ ep_node = of_find_node_by_name(np, "endpoint");
+ if (!ep_node)
+ return NULL;
+ sdi_node = of_graph_get_remote_port_parent(ep_node);
+ of_node_put(ep_node);
+ if (!sdi_node)
+ return NULL;
+
+ mutex_lock(&xilinx_sdi_lock);
+ list_for_each_entry(sdi, &xilinx_sdi_list, list) {
+ if (sdi->dev->of_node == sdi_node) {
+ found = sdi;
+ break;
+ }
+ }
+ mutex_unlock(&xilinx_sdi_lock);
+
+ of_node_put(sdi_node);
+ if (!found)
+ return ERR_PTR(-EPROBE_DEFER);
+ return found;
+}
+
+/**
+ * xilinx_sdi_unregister_device - Unregister the SDI subsystem instance
+ * @sdi: SDI subsystem
+ *
+ * Unregister the SDI subsystem instance from the global list
+ */
+static void xilinx_sdi_unregister_device(struct xilinx_sdi *sdi)
+{
+ mutex_lock(&xilinx_sdi_lock);
+ list_del(&sdi->list);
+ mutex_unlock(&xilinx_sdi_lock);
+}
+
static int xilinx_sdi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
if (ret < 0)
return ret;
+ irq = platform_get_irq(pdev, 1); /* vblank interrupt */
+ if (irq < 0)
+ return irq;
+ ret = devm_request_threaded_irq(sdi->dev, irq, NULL,
+ xilinx_sdi_vblank_handler, IRQF_ONESHOT,
+ "sdiTx-vblank", sdi);
+ if (ret < 0)
+ return ret;
+
init_waitqueue_head(&sdi->wait_event);
sdi->event_received = false;
+ xilinx_sdi_register_device(sdi);
return component_add(dev, &xilinx_sdi_component_ops);
}
static int xilinx_sdi_remove(struct platform_device *pdev)
{
+ struct xilinx_sdi *sdi = platform_get_drvdata(pdev);
+
+ xilinx_sdi_unregister_device(sdi);
component_del(&pdev->dev, &xilinx_sdi_component_ops);
return 0;
--- /dev/null
+/*
+ * SDI subsystem header for Xilinx DRM KMS
+ *
+ * Copyright (C) 2017 Xilinx, Inc.
+ *
+ * Author: Saurabh Sengar <saurabhs@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _XILINX_DRM_SDI_H_
+#define _XILINX_DRM_SDI_H_
+
+struct xilinx_sdi;
+struct device_node;
+
+struct xilinx_sdi *xilinx_drm_sdi_of_get(struct device_node *np);
+void xilinx_drm_sdi_enable_vblank(struct xilinx_sdi *sdi,
+ void (*vblank_fn)(void *),
+ void *vblank_data);
+void xilinx_drm_sdi_disable_vblank(struct xilinx_sdi *sdi);
+#endif /* _XILINX_DRM_SDI_H_ */