ti-dma-crossbar.c 5.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 *  Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com
 *  Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/idr.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_dma.h>

#define TI_XBAR_OUTPUTS	127
#define TI_XBAR_INPUTS	256

23 24 25
#define TI_XBAR_EDMA_OFFSET	0
#define TI_XBAR_SDMA_OFFSET	1

26 27 28 29
struct ti_dma_xbar_data {
	void __iomem *iomem;

	struct dma_router dmarouter;
30
	struct idr map_idr;
31 32 33 34

	u16 safe_val; /* Value to rest the crossbar lines */
	u32 xbar_requests; /* number of DMA requests connected to XBAR */
	u32 dma_requests; /* number of DMA requests forwarded to DMA */
35
	u32 dma_offset;
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
};

struct ti_dma_xbar_map {
	u16 xbar_in;
	int xbar_out;
};

static inline void ti_dma_xbar_write(void __iomem *iomem, int xbar, u16 val)
{
	writew_relaxed(val, iomem + (xbar * 2));
}

static void ti_dma_xbar_free(struct device *dev, void *route_data)
{
	struct ti_dma_xbar_data *xbar = dev_get_drvdata(dev);
	struct ti_dma_xbar_map *map = route_data;

	dev_dbg(dev, "Unmapping XBAR%u (was routed to %d)\n",
		map->xbar_in, map->xbar_out);

	ti_dma_xbar_write(xbar->iomem, map->xbar_out, xbar->safe_val);
57
	idr_remove(&xbar->map_idr, map->xbar_out);
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
	kfree(map);
}

static void *ti_dma_xbar_route_allocate(struct of_phandle_args *dma_spec,
					struct of_dma *ofdma)
{
	struct platform_device *pdev = of_find_device_by_node(ofdma->of_node);
	struct ti_dma_xbar_data *xbar = platform_get_drvdata(pdev);
	struct ti_dma_xbar_map *map;

	if (dma_spec->args[0] >= xbar->xbar_requests) {
		dev_err(&pdev->dev, "Invalid XBAR request number: %d\n",
			dma_spec->args[0]);
		return ERR_PTR(-EINVAL);
	}

	/* The of_node_put() will be done in the core for the node */
	dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0);
	if (!dma_spec->np) {
		dev_err(&pdev->dev, "Can't get DMA master\n");
		return ERR_PTR(-EINVAL);
	}

	map = kzalloc(sizeof(*map), GFP_KERNEL);
	if (!map) {
		of_node_put(dma_spec->np);
		return ERR_PTR(-ENOMEM);
	}

87
	map->xbar_out = idr_alloc(&xbar->map_idr, NULL, 0, xbar->dma_requests,
88 89 90
				  GFP_KERNEL);
	map->xbar_in = (u16)dma_spec->args[0];

91
	dma_spec->args[0] = map->xbar_out + xbar->dma_offset;
92 93 94 95 96 97 98 99 100

	dev_dbg(&pdev->dev, "Mapping XBAR%u to DMA%d\n",
		map->xbar_in, map->xbar_out);

	ti_dma_xbar_write(xbar->iomem, map->xbar_out, map->xbar_in);

	return map;
}

101 102 103 104 105 106 107 108 109 110 111 112
static const struct of_device_id ti_dma_master_match[] = {
	{
		.compatible = "ti,omap4430-sdma",
		.data = (void *)TI_XBAR_SDMA_OFFSET,
	},
	{
		.compatible = "ti,edma3",
		.data = (void *)TI_XBAR_EDMA_OFFSET,
	},
	{},
};

113 114 115
static int ti_dma_xbar_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
116
	const struct of_device_id *match;
117 118 119 120 121 122 123 124 125 126 127 128 129 130
	struct device_node *dma_node;
	struct ti_dma_xbar_data *xbar;
	struct resource *res;
	u32 safe_val;
	void __iomem *iomem;
	int i, ret;

	if (!node)
		return -ENODEV;

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

131 132
	idr_init(&xbar->map_idr);

133 134 135 136 137 138
	dma_node = of_parse_phandle(node, "dma-masters", 0);
	if (!dma_node) {
		dev_err(&pdev->dev, "Can't get DMA master node\n");
		return -ENODEV;
	}

139 140 141 142 143 144
	match = of_match_node(ti_dma_master_match, dma_node);
	if (!match) {
		dev_err(&pdev->dev, "DMA master is not supported\n");
		return -EINVAL;
	}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
	if (of_property_read_u32(dma_node, "dma-requests",
				 &xbar->dma_requests)) {
		dev_info(&pdev->dev,
			 "Missing XBAR output information, using %u.\n",
			 TI_XBAR_OUTPUTS);
		xbar->dma_requests = TI_XBAR_OUTPUTS;
	}
	of_node_put(dma_node);

	if (of_property_read_u32(node, "dma-requests", &xbar->xbar_requests)) {
		dev_info(&pdev->dev,
			 "Missing XBAR input information, using %u.\n",
			 TI_XBAR_INPUTS);
		xbar->xbar_requests = TI_XBAR_INPUTS;
	}

	if (!of_property_read_u32(node, "ti,dma-safe-map", &safe_val))
		xbar->safe_val = (u16)safe_val;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	iomem = devm_ioremap_resource(&pdev->dev, res);
166 167
	if (IS_ERR(iomem))
		return PTR_ERR(iomem);
168 169 170 171 172

	xbar->iomem = iomem;

	xbar->dmarouter.dev = &pdev->dev;
	xbar->dmarouter.route_free = ti_dma_xbar_free;
173
	xbar->dma_offset = (u32)match->data;
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209

	platform_set_drvdata(pdev, xbar);

	/* Reset the crossbar */
	for (i = 0; i < xbar->dma_requests; i++)
		ti_dma_xbar_write(xbar->iomem, i, xbar->safe_val);

	ret = of_dma_router_register(node, ti_dma_xbar_route_allocate,
				     &xbar->dmarouter);
	if (ret) {
		/* Restore the defaults for the crossbar */
		for (i = 0; i < xbar->dma_requests; i++)
			ti_dma_xbar_write(xbar->iomem, i, i);
	}

	return ret;
}

static const struct of_device_id ti_dma_xbar_match[] = {
	{ .compatible = "ti,dra7-dma-crossbar" },
	{},
};

static struct platform_driver ti_dma_xbar_driver = {
	.driver = {
		.name = "ti-dma-crossbar",
		.of_match_table = of_match_ptr(ti_dma_xbar_match),
	},
	.probe	= ti_dma_xbar_probe,
};

int omap_dmaxbar_init(void)
{
	return platform_driver_register(&ti_dma_xbar_driver);
}
arch_initcall(omap_dmaxbar_init);