summaryrefslogtreecommitdiffstats
path: root/drivers/dma/acpi-dma.c
blob: 5a18f82f732af57a319628190713e6bd054cf8b3 (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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/*
 * ACPI helpers for DMA request / controller
 *
 * Based on of-dma.c
 *
 * Copyright (C) 2013, Intel Corporation
 * Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
 *	    Mika Westerberg <mika.westerberg@linux.intel.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/device.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/acpi.h>
#include <linux/acpi_dma.h>

static LIST_HEAD(acpi_dma_list);
static DEFINE_MUTEX(acpi_dma_lock);

/**
 * acpi_dma_parse_resource_group - match device and parse resource group
 * @grp:	CSRT resource group
 * @adev:	ACPI device to match with
 * @adma:	struct acpi_dma of the given DMA controller
 *
 * Returns 1 on success, 0 when no information is available, or appropriate
 * errno value on error.
 *
 * In order to match a device from DSDT table to the corresponding CSRT device
 * we use MMIO address and IRQ.
 */
static int acpi_dma_parse_resource_group(const struct acpi_csrt_group *grp,
		struct acpi_device *adev, struct acpi_dma *adma)
{
	const struct acpi_csrt_shared_info *si;
	struct list_head resource_list;
	struct resource_list_entry *rentry;
	resource_size_t mem = 0, irq = 0;
	u32 vendor_id;
	int ret;

	if (grp->shared_info_length != sizeof(struct acpi_csrt_shared_info))
		return -ENODEV;

	INIT_LIST_HEAD(&resource_list);
	ret = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
	if (ret <= 0)
		return 0;

	list_for_each_entry(rentry, &resource_list, node) {
		if (resource_type(&rentry->res) == IORESOURCE_MEM)
			mem = rentry->res.start;
		else if (resource_type(&rentry->res) == IORESOURCE_IRQ)
			irq = rentry->res.start;
	}

	acpi_dev_free_resource_list(&resource_list);

	/* Consider initial zero values as resource not found */
	if (mem == 0 && irq == 0)
		return 0;

	si = (const struct acpi_csrt_shared_info *)&grp[1];

	/* Match device by MMIO and IRQ */
	if (si->mmio_base_low != mem || si->gsi_interrupt != irq)
		return 0;

	vendor_id = le32_to_cpu(grp->vendor_id);
	dev_dbg(&adev->dev, "matches with %.4s%04X (rev %u)\n",
		(char *)&vendor_id, grp->device_id, grp->revision);

	/* Check if the request line range is available */
	if (si->base_request_line == 0 && si->num_handshake_signals == 0)
		return 0;

	adma->base_request_line = si->base_request_line;
	adma->end_request_line = si->base_request_line +
				 si->num_handshake_signals - 1;

	dev_dbg(&adev->dev, "request line base: 0x%04x end: 0x%04x\n",
		adma->base_request_line, adma->end_request_line);

	return 1;
}

/**
 * acpi_dma_parse_csrt - parse CSRT to exctract additional DMA resources
 * @adev:	ACPI device to match with
 * @adma:	struct acpi_dma of the given DMA controller
 *
 * CSRT or Core System Resources Table is a proprietary ACPI table
 * introduced by Microsoft. This table can contain devices that are not in
 * the system DSDT table. In particular DMA controllers might be described
 * here.
 *
 * We are using this table to get the request line range of the specific DMA
 * controller to be used later.
 *
 */
static void acpi_dma_parse_csrt(struct acpi_device *adev, struct acpi_dma *adma)
{
	struct acpi_csrt_group *grp, *end;
	struct acpi_table_csrt *csrt;
	acpi_status status;
	int ret;

	status = acpi_get_table(ACPI_SIG_CSRT, 0,
				(struct acpi_table_header **)&csrt);
	if (ACPI_FAILURE(status)) {
		if (status != AE_NOT_FOUND)
			dev_warn(&adev->dev, "failed to get the CSRT table\n");
		return;
	}

	grp = (struct acpi_csrt_group *)(csrt + 1);
	end = (struct acpi_csrt_group *)((void *)csrt + csrt->header.length);

	while (grp < end) {
		ret = acpi_dma_parse_resource_group(grp, adev, adma);
		if (ret < 0) {
			dev_warn(&adev->dev,
				 "error in parsing resource group\n");
			return;
		}

		grp = (struct acpi_csrt_group *)((void *)grp + grp->length);
	}
}

/**
 * acpi_dma_controller_register - Register a DMA controller to ACPI DMA helpers
 * @dev:		struct device of DMA controller
 * @acpi_dma_xlate:	translation function which converts a dma specifier
 *			into a dma_chan structure
 * @data		pointer to controller specific data to be used by
 *			translation function
 *
 * Returns 0 on success or appropriate errno value on error.
 *
 * Allocated memory should be freed with appropriate acpi_dma_controller_free()
 * call.
 */
int acpi_dma_controller_register(struct device *dev,
		struct dma_chan *(*acpi_dma_xlate)
		(struct acpi_dma_spec *, struct acpi_dma *),
		void *data)
{
	struct acpi_device *adev;
	struct acpi_dma	*adma;

	if (!dev || !acpi_dma_xlate)
		return -EINVAL;

	/* Check if the device was enumerated by ACPI */
	if (!ACPI_HANDLE(dev))
		return -EINVAL;

	if (acpi_bus_get_device(ACPI_HANDLE(dev), &adev))
		return -EINVAL;

	adma = kzalloc(sizeof(*adma), GFP_KERNEL);
	if (!adma)
		return -ENOMEM;

	adma->dev = dev;
	adma->acpi_dma_xlate = acpi_dma_xlate;
	adma->data = data;

	acpi_dma_parse_csrt(adev, adma);

	/* Now queue acpi_dma controller structure in list */
	mutex_lock(&acpi_dma_lock);
	list_add_tail(&adma->dma_controllers, &acpi_dma_list);
	mutex_unlock(&acpi_dma_lock);

	return 0;
}
EXPORT_SYMBOL_GPL(acpi_dma_controller_register);

/**
 * acpi_dma_controller_free - Remove a DMA controller from ACPI DMA helpers list
 * @dev:	struct device of DMA controller
 *
 * Memory allocated by acpi_dma_controller_register() is freed here.
 */
int acpi_dma_controller_free(struct device *dev)
{
	struct acpi_dma *adma;

	if (!dev)
		return -EINVAL;

	mutex_lock(&acpi_dma_lock);

	list_for_each_entry(adma, &acpi_dma_list, dma_controllers)
		if (adma->dev == dev) {
			list_del(&adma->dma_controllers);
			mutex_unlock(&acpi_dma_lock);
			kfree(adma);
			return 0;
		}

	mutex_unlock(&acpi_dma_lock);
	return -ENODEV;
}
EXPORT_SYMBOL_GPL(acpi_dma_controller_free);

static void devm_acpi_dma_release(struct device *dev, void *res)
{
	acpi_dma_controller_free(dev);
}

/**
 * devm_acpi_dma_controller_register - resource managed acpi_dma_controller_register()
 * @dev:		device that is registering this DMA controller
 * @acpi_dma_xlate:	translation function
 * @data		pointer to controller specific data
 *
 * Managed acpi_dma_controller_register(). DMA controller registered by this
 * function are automatically freed on driver detach. See
 * acpi_dma_controller_register() for more information.
 */
int devm_acpi_dma_controller_register(struct device *dev,
		struct dma_chan *(*acpi_dma_xlate)
		(struct acpi_dma_spec *, struct acpi_dma *),
		void *data)
{
	void *res;
	int ret;

	res = devres_alloc(devm_acpi_dma_release, 0, GFP_KERNEL);
	if (!res)
		return -ENOMEM;

	ret = acpi_dma_controller_register(dev, acpi_dma_xlate, data);
	if (ret) {
		devres_free(res);
		return ret;
	}
	devres_add(dev, res);
	return 0;
}
EXPORT_SYMBOL_GPL(devm_acpi_dma_controller_register);

/**
 * devm_acpi_dma_controller_free - resource managed acpi_dma_controller_free()
 *
 * Unregister a DMA controller registered with
 * devm_acpi_dma_controller_register(). Normally this function will not need to
 * be called and the resource management code will ensure that the resource is
 * freed.
 */
void devm_acpi_dma_controller_free(struct device *dev)
{
	WARN_ON(devres_destroy(dev, devm_acpi_dma_release, NULL, NULL));
}
EXPORT_SYMBOL_GPL(devm_acpi_dma_controller_free);

/**
 * acpi_dma_update_dma_spec - prepare dma specifier to pass to translation function
 * @adma:	struct acpi_dma of DMA controller
 * @dma_spec:	dma specifier to update
 *
 * Returns 0, if no information is avaiable, -1 on mismatch, and 1 otherwise.
 *
 * Accordingly to ACPI 5.0 Specification Table 6-170 "Fixed DMA Resource
 * Descriptor":
 *	DMA Request Line bits is a platform-relative number uniquely
 *	identifying the request line assigned. Request line-to-Controller
 *	mapping is done in a controller-specific OS driver.
 * That's why we can safely adjust slave_id when the appropriate controller is
 * found.
 */
static int acpi_dma_update_dma_spec(struct acpi_dma *adma,
		struct acpi_dma_spec *dma_spec)
{
	/* Set link to the DMA controller device */
	dma_spec->dev = adma->dev;

	/* Check if the request line range is available */
	if (adma->base_request_line == 0 && adma->end_request_line == 0)
		return 0;

	/* Check if slave_id falls to the range */
	if (dma_spec->slave_id < adma->base_request_line ||
	    dma_spec->slave_id > adma->end_request_line)
		return -1;

	/*
	 * Here we adjust slave_id. It should be a relative number to the base
	 * request line.
	 */
	dma_spec->slave_id -= adma->base_request_line;

	return 1;
}

struct acpi_dma_parser_data {
	struct acpi_dma_spec dma_spec;
	size_t index;
	size_t n;
};

/**
 * acpi_dma_parse_fixed_dma - Parse FixedDMA ACPI resources to a DMA specifier
 * @res:	struct acpi_resource to get FixedDMA resources from
 * @data:	pointer to a helper struct acpi_dma_parser_data
 */
static int acpi_dma_parse_fixed_dma(struct acpi_resource *res, void *data)
{
	struct acpi_dma_parser_data *pdata = data;

	if (res->type == ACPI_RESOURCE_TYPE_FIXED_DMA) {
		struct acpi_resource_fixed_dma *dma = &res->data.fixed_dma;

		if (pdata->n++ == pdata->index) {
			pdata->dma_spec.chan_id = dma->channels;
			pdata->dma_spec.slave_id = dma->request_lines;
		}
	}

	/* Tell the ACPI core to skip this resource */
	return 1;
}

/**
 * acpi_dma_request_slave_chan_by_index - Get the DMA slave channel
 * @dev:	struct device to get DMA request from
 * @index:	index of FixedDMA descriptor for @dev
 *
 * Returns pointer to appropriate dma channel on success or NULL on error.
 */
struct dma_chan *acpi_dma_request_slave_chan_by_index(struct device *dev,
		size_t index)
{
	struct acpi_dma_parser_data pdata;
	struct acpi_dma_spec *dma_spec = &pdata.dma_spec;
	struct list_head resource_list;
	struct acpi_device *adev;
	struct acpi_dma *adma;
	struct dma_chan *chan = NULL;
	int found;

	/* Check if the device was enumerated by ACPI */
	if (!dev || !ACPI_HANDLE(dev))
		return NULL;

	if (acpi_bus_get_device(ACPI_HANDLE(dev), &adev))
		return NULL;

	memset(&pdata, 0, sizeof(pdata));
	pdata.index = index;

	/* Initial values for the request line and channel */
	dma_spec->chan_id = -1;
	dma_spec->slave_id = -1;

	INIT_LIST_HEAD(&resource_list);
	acpi_dev_get_resources(adev, &resource_list,
			acpi_dma_parse_fixed_dma, &pdata);
	acpi_dev_free_resource_list(&resource_list);

	if (dma_spec->slave_id < 0 || dma_spec->chan_id < 0)
		return NULL;

	mutex_lock(&acpi_dma_lock);

	list_for_each_entry(adma, &acpi_dma_list, dma_controllers) {
		/*
		 * We are not going to call translation function if slave_id
		 * doesn't fall to the request range.
		 */
		found = acpi_dma_update_dma_spec(adma, dma_spec);
		if (found < 0)
			continue;
		chan = adma->acpi_dma_xlate(dma_spec, adma);
		/*
		 * Try to get a channel only from the DMA controller that
		 * matches the slave_id. See acpi_dma_update_dma_spec()
		 * description for the details.
		 */
		if (found > 0 || chan)
			break;
	}

	mutex_unlock(&acpi_dma_lock);
	return chan;
}
EXPORT_SYMBOL_GPL(acpi_dma_request_slave_chan_by_index);

/**
 * acpi_dma_request_slave_chan_by_name - Get the DMA slave channel
 * @dev:	struct device to get DMA request from
 * @name:	represents corresponding FixedDMA descriptor for @dev
 *
 * In order to support both Device Tree and ACPI in a single driver we
 * translate the names "tx" and "rx" here based on the most common case where
 * the first FixedDMA descriptor is TX and second is RX.
 *
 * Returns pointer to appropriate dma channel on success or NULL on error.
 */
struct dma_chan *acpi_dma_request_slave_chan_by_name(struct device *dev,
		const char *name)
{
	size_t index;

	if (!strcmp(name, "tx"))
		index = 0;
	else if (!strcmp(name, "rx"))
		index = 1;
	else
		return NULL;

	return acpi_dma_request_slave_chan_by_index(dev, index);
}
EXPORT_SYMBOL_GPL(acpi_dma_request_slave_chan_by_name);

/**
 * acpi_dma_simple_xlate - Simple ACPI DMA engine translation helper
 * @dma_spec: pointer to ACPI DMA specifier
 * @adma: pointer to ACPI DMA controller data
 *
 * A simple translation function for ACPI based devices. Passes &struct
 * dma_spec to the DMA controller driver provided filter function. Returns
 * pointer to the channel if found or %NULL otherwise.
 */
struct dma_chan *acpi_dma_simple_xlate(struct acpi_dma_spec *dma_spec,
		struct acpi_dma *adma)
{
	struct acpi_dma_filter_info *info = adma->data;

	if (!info || !info->filter_fn)
		return NULL;

	return dma_request_channel(info->dma_cap, info->filter_fn, dma_spec);
}
EXPORT_SYMBOL_GPL(acpi_dma_simple_xlate);
OpenPOWER on IntegriCloud