aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/xilinx/xilinx-scenechange.c
blob: 9135355934fe0e5a69f717c3d4fa80eb4637eacc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//SPDX-License-Identifier: GPL-2.0
/*
 * Xilinx Scene Change Detection driver
 *
 * Copyright (C) 2018 Xilinx, Inc.
 *
 * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
 *          Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
 */

#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#include "xilinx-scenechange.h"

#define XSCD_RESET_DEASSERT	(0)
#define XSCD_RESET_ASSERT	(1)

static irqreturn_t xscd_irq_handler(int irq, void *data)
{
	struct xscd_device *xscd = (struct xscd_device *)data;
	u32 status;

	status = xscd_read(xscd->iomem, XSCD_ISR_OFFSET);
	if (!(status & XSCD_IE_AP_DONE))
		return IRQ_NONE;

	xscd_write(xscd->iomem, XSCD_ISR_OFFSET, XSCD_IE_AP_DONE);

	if (xscd->memory_based)
		xscd_dma_irq_handler(xscd);
	else
		xscd_chan_event_notify(&xscd->chans[0]);

	return IRQ_HANDLED;
}

static int xscd_init_resources(struct xscd_device *xscd)
{
	struct platform_device *pdev = to_platform_device(xscd->dev);
	struct resource *res;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	xscd->iomem = devm_ioremap_resource(xscd->dev, res);
	if (IS_ERR(xscd->iomem))
		return PTR_ERR(xscd->iomem);

	xscd->irq = platform_get_irq(pdev, 0);
	if (xscd->irq < 0) {
		dev_err(xscd->dev, "No valid irq found\n");
		return -EINVAL;
	}

	xscd->clk = devm_clk_get(xscd->dev, NULL);
	if (IS_ERR(xscd->clk))
		return PTR_ERR(xscd->clk);

	clk_prepare_enable(xscd->clk);
	return 0;
}

static int xscd_parse_of(struct xscd_device *xscd)
{
	struct device *dev = xscd->dev;
	struct device_node *node = xscd->dev->of_node;
	int ret;

	xscd->memory_based = of_property_read_bool(node, "xlnx,memorybased");
	xscd->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(xscd->rst_gpio)) {
		if (PTR_ERR(xscd->rst_gpio) != -EPROBE_DEFER)
			dev_err(dev, "Reset GPIO not setup in DT\n");

		return PTR_ERR(xscd->rst_gpio);
	}

	ret = of_property_read_u32(node, "xlnx,numstreams",
				   &xscd->num_streams);
	if (ret < 0)
		return ret;

	if (!xscd->memory_based && xscd->num_streams != 1) {
		dev_err(dev, "Stream-based mode only supports one stream\n");
		return -EINVAL;
	}

	return 0;
}

static int xscd_probe(struct platform_device *pdev)
{
	struct xscd_device *xscd;
	struct device_node *subdev_node;
	unsigned int id;
	int ret;

	xscd = devm_kzalloc(&pdev->dev, sizeof(*xscd), GFP_KERNEL);
	if (!xscd)
		return -ENOMEM;

	spin_lock_init(&xscd->lock);

	xscd->dev = &pdev->dev;
	platform_set_drvdata(pdev, xscd);

	ret = xscd_parse_of(xscd);
	if (ret < 0)
		return ret;

	ret = xscd_init_resources(xscd);
	if (ret < 0)
		return ret;

	/* Reset Scene Change Detection IP */
	gpiod_set_value_cansleep(xscd->rst_gpio, XSCD_RESET_ASSERT);
	gpiod_set_value_cansleep(xscd->rst_gpio, XSCD_RESET_DEASSERT);

	/* Initialize the channels. */
	xscd->chans = devm_kcalloc(xscd->dev, xscd->num_streams,
				   sizeof(*xscd->chans), GFP_KERNEL);
	if (!xscd->chans)
		return -ENOMEM;

	id = 0;
	for_each_child_of_node(xscd->dev->of_node, subdev_node) {
		if (id >= xscd->num_streams) {
			dev_warn(&pdev->dev,
				 "Too many channels, limiting to %u\n",
				 xscd->num_streams);
			of_node_put(subdev_node);
			break;
		}

		ret = xscd_chan_init(xscd, id, subdev_node);
		if (ret < 0) {
			dev_err(&pdev->dev, "Failed to initialize channel %u\n",
				id);
			return ret;
		}

		id++;
	}

	/* Initialize the DMA engine. */
	ret = xscd_dma_init(xscd);
	if (ret < 0)
		dev_err(&pdev->dev, "Failed to initialize the DMA\n");

	ret = devm_request_irq(xscd->dev, xscd->irq, xscd_irq_handler,
			       IRQF_SHARED, dev_name(xscd->dev), xscd);
	if (ret < 0)
		dev_err(&pdev->dev, "Failed to request IRQ\n");

	dev_info(xscd->dev, "scene change detect device found!\n");
	return 0;
}

static int xscd_remove(struct platform_device *pdev)
{
	struct xscd_device *xscd = platform_get_drvdata(pdev);

	xscd_dma_cleanup(xscd);
	clk_disable_unprepare(xscd->clk);

	return 0;
}

static const struct of_device_id xscd_of_id_table[] = {
	{ .compatible = "xlnx,v-scd" },
	{ }
};
MODULE_DEVICE_TABLE(of, xscd_of_id_table);

static struct platform_driver xscd_driver = {
	.driver = {
		.name		= "xilinx-scd",
		.of_match_table	= xscd_of_id_table,
	},
	.probe			= xscd_probe,
	.remove			= xscd_remove,
};

module_platform_driver(xscd_driver);

MODULE_AUTHOR("Xilinx Inc.");
MODULE_DESCRIPTION("Xilinx Scene Change Detection");
MODULE_LICENSE("GPL v2");