File Index
Files and directories
Sign in
FILE

loongson1-apb-dma.c

File preview with syntax highlighting.

linux-6.19.14/drivers/dma/loongson1-apb-dma.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Driver for Loongson-1 APB DMA Controller
  4 *
  5 * Copyright (C) 2015-2024 Keguang Zhang <keguang.zhang@gmail.com>
  6 */
  7
  8#include <linux/dmapool.h>
  9#include <linux/dma-mapping.h>
 10#include <linux/init.h>
 11#include <linux/interrupt.h>
 12#include <linux/iopoll.h>
 13#include <linux/module.h>
 14#include <linux/of.h>
 15#include <linux/of_dma.h>
 16#include <linux/platform_device.h>
 17#include <linux/slab.h>
 18
 19#include "dmaengine.h"
 20#include "virt-dma.h"
 21
 22/* Loongson-1 DMA Control Register */
 23#define LS1X_DMA_CTRL		0x0
 24
 25/* DMA Control Register Bits */
 26#define LS1X_DMA_STOP		BIT(4)
 27#define LS1X_DMA_START		BIT(3)
 28#define LS1X_DMA_ASK_VALID	BIT(2)
 29
 30/* DMA Next Field Bits */
 31#define LS1X_DMA_NEXT_VALID	BIT(0)
 32
 33/* DMA Command Field Bits */
 34#define LS1X_DMA_RAM2DEV	BIT(12)
 35#define LS1X_DMA_INT		BIT(1)
 36#define LS1X_DMA_INT_MASK	BIT(0)
 37
 38#define LS1X_DMA_LLI_ALIGNMENT	64
 39#define LS1X_DMA_LLI_ADDR_MASK	GENMASK(31, __ffs(LS1X_DMA_LLI_ALIGNMENT))
 40#define LS1X_DMA_MAX_CHANNELS	3
 41
 42enum ls1x_dmadesc_offsets {
 43	LS1X_DMADESC_NEXT = 0,
 44	LS1X_DMADESC_SADDR,
 45	LS1X_DMADESC_DADDR,
 46	LS1X_DMADESC_LENGTH,
 47	LS1X_DMADESC_STRIDE,
 48	LS1X_DMADESC_CYCLES,
 49	LS1X_DMADESC_CMD,
 50	LS1X_DMADESC_SIZE
 51};
 52
 53struct ls1x_dma_lli {
 54	unsigned int hw[LS1X_DMADESC_SIZE];
 55	dma_addr_t phys;
 56	struct list_head node;
 57} __aligned(LS1X_DMA_LLI_ALIGNMENT);
 58
 59struct ls1x_dma_desc {
 60	struct virt_dma_desc vd;
 61	struct list_head lli_list;
 62};
 63
 64struct ls1x_dma_chan {
 65	struct virt_dma_chan vc;
 66	struct dma_pool *lli_pool;
 67	phys_addr_t src_addr;
 68	phys_addr_t dst_addr;
 69	enum dma_slave_buswidth src_addr_width;
 70	enum dma_slave_buswidth dst_addr_width;
 71	unsigned int bus_width;
 72	void __iomem *reg_base;
 73	int irq;
 74	bool is_cyclic;
 75	struct ls1x_dma_lli *curr_lli;
 76};
 77
 78struct ls1x_dma {
 79	struct dma_device ddev;
 80	unsigned int nr_chans;
 81	struct ls1x_dma_chan chan[];
 82};
 83
 84static irqreturn_t ls1x_dma_irq_handler(int irq, void *data);
 85
 86#define to_ls1x_dma_chan(dchan)		\
 87	container_of(dchan, struct ls1x_dma_chan, vc.chan)
 88
 89#define to_ls1x_dma_desc(d)		\
 90	container_of(d, struct ls1x_dma_desc, vd)
 91
 92static inline struct device *chan2dev(struct dma_chan *chan)
 93{
 94	return &chan->dev->device;
 95}
 96
 97static inline int ls1x_dma_query(struct ls1x_dma_chan *chan,
 98				 dma_addr_t *lli_phys)
 99{
100	struct dma_chan *dchan = &chan->vc.chan;
101	int val, ret;
102
103	val = *lli_phys & LS1X_DMA_LLI_ADDR_MASK;
104	val |= LS1X_DMA_ASK_VALID;
105	val |= dchan->chan_id;
106	writel(val, chan->reg_base + LS1X_DMA_CTRL);
107	ret = readl_poll_timeout_atomic(chan->reg_base + LS1X_DMA_CTRL, val,
108					!(val & LS1X_DMA_ASK_VALID), 0, 3000);
109	if (ret)
110		dev_err(chan2dev(dchan), "failed to query DMA\n");
111
112	return ret;
113}
114
115static inline int ls1x_dma_start(struct ls1x_dma_chan *chan,
116				 dma_addr_t *lli_phys)
117{
118	struct dma_chan *dchan = &chan->vc.chan;
119	struct device *dev = chan2dev(dchan);
120	int val, ret;
121
122	val = *lli_phys & LS1X_DMA_LLI_ADDR_MASK;
123	val |= LS1X_DMA_START;
124	val |= dchan->chan_id;
125	writel(val, chan->reg_base + LS1X_DMA_CTRL);
126	ret = readl_poll_timeout(chan->reg_base + LS1X_DMA_CTRL, val,
127				 !(val & LS1X_DMA_START), 0, 1000);
128	if (!ret)
129		dev_dbg(dev, "start DMA with lli_phys=%pad\n", lli_phys);
130	else
131		dev_err(dev, "failed to start DMA\n");
132
133	return ret;
134}
135
136static inline void ls1x_dma_stop(struct ls1x_dma_chan *chan)
137{
138	int val = readl(chan->reg_base + LS1X_DMA_CTRL);
139
140	writel(val | LS1X_DMA_STOP, chan->reg_base + LS1X_DMA_CTRL);
141}
142
143static void ls1x_dma_free_chan_resources(struct dma_chan *dchan)
144{
145	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
146	struct device *dev = chan2dev(dchan);
147
148	dma_free_coherent(dev, sizeof(struct ls1x_dma_lli),
149			  chan->curr_lli, chan->curr_lli->phys);
150	dma_pool_destroy(chan->lli_pool);
151	chan->lli_pool = NULL;
152	devm_free_irq(dev, chan->irq, chan);
153	vchan_free_chan_resources(&chan->vc);
154}
155
156static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan)
157{
158	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
159	struct device *dev = chan2dev(dchan);
160	dma_addr_t phys;
161	int ret;
162
163	ret = devm_request_irq(dev, chan->irq, ls1x_dma_irq_handler,
164			       IRQF_SHARED, dma_chan_name(dchan), chan);
165	if (ret) {
166		dev_err(dev, "failed to request IRQ %d\n", chan->irq);
167		return ret;
168	}
169
170	chan->lli_pool = dma_pool_create(dma_chan_name(dchan), dev,
171					 sizeof(struct ls1x_dma_lli),
172					 __alignof__(struct ls1x_dma_lli), 0);
173	if (!chan->lli_pool)
174		return -ENOMEM;
175
176	/* allocate memory for querying the current lli */
177	dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
178	chan->curr_lli = dma_alloc_coherent(dev, sizeof(struct ls1x_dma_lli),
179					    &phys, GFP_KERNEL);
180	if (!chan->curr_lli) {
181		dma_pool_destroy(chan->lli_pool);
182		return -ENOMEM;
183	}
184	chan->curr_lli->phys = phys;
185
186	return 0;
187}
188
189static void ls1x_dma_free_desc(struct virt_dma_desc *vd)
190{
191	struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);
192	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(vd->tx.chan);
193	struct ls1x_dma_lli *lli, *_lli;
194
195	list_for_each_entry_safe(lli, _lli, &desc->lli_list, node) {
196		list_del(&lli->node);
197		dma_pool_free(chan->lli_pool, lli, lli->phys);
198	}
199
200	kfree(desc);
201}
202
203static struct ls1x_dma_desc *ls1x_dma_alloc_desc(void)
204{
205	struct ls1x_dma_desc *desc;
206
207	desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
208	if (!desc)
209		return NULL;
210
211	INIT_LIST_HEAD(&desc->lli_list);
212
213	return desc;
214}
215
216static int ls1x_dma_prep_lli(struct dma_chan *dchan, struct ls1x_dma_desc *desc,
217			     struct scatterlist *sgl, unsigned int sg_len,
218			     enum dma_transfer_direction dir, bool is_cyclic)
219{
220	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
221	struct ls1x_dma_lli *lli, *prev = NULL, *first = NULL;
222	struct device *dev = chan2dev(dchan);
223	struct list_head *pos = NULL;
224	struct scatterlist *sg;
225	unsigned int dev_addr, cmd, i;
226
227	switch (dir) {
228	case DMA_MEM_TO_DEV:
229		dev_addr = chan->dst_addr;
230		chan->bus_width = chan->dst_addr_width;
231		cmd = LS1X_DMA_RAM2DEV | LS1X_DMA_INT;
232		break;
233	case DMA_DEV_TO_MEM:
234		dev_addr = chan->src_addr;
235		chan->bus_width = chan->src_addr_width;
236		cmd = LS1X_DMA_INT;
237		break;
238	default:
239		dev_err(dev, "unsupported DMA direction: %s\n",
240			dmaengine_get_direction_text(dir));
241		return -EINVAL;
242	}
243
244	for_each_sg(sgl, sg, sg_len, i) {
245		dma_addr_t buf_addr = sg_dma_address(sg);
246		size_t buf_len = sg_dma_len(sg);
247		dma_addr_t phys;
248
249		if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) {
250			dev_err(dev, "buffer is not aligned\n");
251			return -EINVAL;
252		}
253
254		/* allocate HW descriptors */
255		lli = dma_pool_zalloc(chan->lli_pool, GFP_NOWAIT, &phys);
256		if (!lli) {
257			dev_err(dev, "failed to alloc lli %u\n", i);
258			return -ENOMEM;
259		}
260
261		/* setup HW descriptors */
262		lli->phys = phys;
263		lli->hw[LS1X_DMADESC_SADDR] = buf_addr;
264		lli->hw[LS1X_DMADESC_DADDR] = dev_addr;
265		lli->hw[LS1X_DMADESC_LENGTH] = buf_len / chan->bus_width;
266		lli->hw[LS1X_DMADESC_STRIDE] = 0;
267		lli->hw[LS1X_DMADESC_CYCLES] = 1;
268		lli->hw[LS1X_DMADESC_CMD] = cmd;
269
270		if (prev)
271			prev->hw[LS1X_DMADESC_NEXT] =
272			    lli->phys | LS1X_DMA_NEXT_VALID;
273		prev = lli;
274
275		if (!first)
276			first = lli;
277
278		list_add_tail(&lli->node, &desc->lli_list);
279	}
280
281	if (is_cyclic) {
282		lli->hw[LS1X_DMADESC_NEXT] = first->phys | LS1X_DMA_NEXT_VALID;
283		chan->is_cyclic = is_cyclic;
284	}
285
286	list_for_each(pos, &desc->lli_list) {
287		lli = list_entry(pos, struct ls1x_dma_lli, node);
288		print_hex_dump_debug("LLI: ", DUMP_PREFIX_OFFSET, 16, 4,
289				     lli, sizeof(*lli), false);
290	}
291
292	return 0;
293}
294
295static struct dma_async_tx_descriptor *
296ls1x_dma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
297		       unsigned int sg_len, enum dma_transfer_direction dir,
298		       unsigned long flags, void *context)
299{
300	struct ls1x_dma_desc *desc;
301
302	dev_dbg(chan2dev(dchan), "sg_len=%u flags=0x%lx dir=%s\n",
303		sg_len, flags, dmaengine_get_direction_text(dir));
304
305	desc = ls1x_dma_alloc_desc();
306	if (!desc)
307		return NULL;
308
309	if (ls1x_dma_prep_lli(dchan, desc, sgl, sg_len, dir, false)) {
310		ls1x_dma_free_desc(&desc->vd);
311		return NULL;
312	}
313
314	return vchan_tx_prep(to_virt_chan(dchan), &desc->vd, flags);
315}
316
317static struct dma_async_tx_descriptor *
318ls1x_dma_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr,
319			 size_t buf_len, size_t period_len,
320			 enum dma_transfer_direction dir, unsigned long flags)
321{
322	struct ls1x_dma_desc *desc;
323	struct scatterlist *sgl;
324	unsigned int sg_len;
325	unsigned int i;
326	int ret;
327
328	dev_dbg(chan2dev(dchan),
329		"buf_len=%zu period_len=%zu flags=0x%lx dir=%s\n",
330		buf_len, period_len, flags, dmaengine_get_direction_text(dir));
331
332	desc = ls1x_dma_alloc_desc();
333	if (!desc)
334		return NULL;
335
336	/* allocate the scatterlist */
337	sg_len = buf_len / period_len;
338	sgl = kmalloc_array(sg_len, sizeof(*sgl), GFP_NOWAIT);
339	if (!sgl)
340		return NULL;
341
342	sg_init_table(sgl, sg_len);
343	for (i = 0; i < sg_len; ++i) {
344		sg_set_page(&sgl[i], pfn_to_page(PFN_DOWN(buf_addr)),
345			    period_len, offset_in_page(buf_addr));
346		sg_dma_address(&sgl[i]) = buf_addr;
347		sg_dma_len(&sgl[i]) = period_len;
348		buf_addr += period_len;
349	}
350
351	ret = ls1x_dma_prep_lli(dchan, desc, sgl, sg_len, dir, true);
352	kfree(sgl);
353	if (ret) {
354		ls1x_dma_free_desc(&desc->vd);
355		return NULL;
356	}
357
358	return vchan_tx_prep(to_virt_chan(dchan), &desc->vd, flags);
359}
360
361static int ls1x_dma_slave_config(struct dma_chan *dchan,
362				 struct dma_slave_config *config)
363{
364	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
365
366	chan->src_addr = config->src_addr;
367	chan->src_addr_width = config->src_addr_width;
368	chan->dst_addr = config->dst_addr;
369	chan->dst_addr_width = config->dst_addr_width;
370
371	return 0;
372}
373
374static int ls1x_dma_pause(struct dma_chan *dchan)
375{
376	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
377	int ret;
378
379	guard(spinlock_irqsave)(&chan->vc.lock);
380	/* save the current lli */
381	ret = ls1x_dma_query(chan, &chan->curr_lli->phys);
382	if (!ret)
383		ls1x_dma_stop(chan);
384
385	return ret;
386}
387
388static int ls1x_dma_resume(struct dma_chan *dchan)
389{
390	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
391
392	guard(spinlock_irqsave)(&chan->vc.lock);
393
394	return ls1x_dma_start(chan, &chan->curr_lli->phys);
395}
396
397static int ls1x_dma_terminate_all(struct dma_chan *dchan)
398{
399	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
400	struct virt_dma_desc *vd;
401	LIST_HEAD(head);
402
403	ls1x_dma_stop(chan);
404
405	scoped_guard(spinlock_irqsave, &chan->vc.lock) {
406		vd = vchan_next_desc(&chan->vc);
407		if (vd)
408			vchan_terminate_vdesc(vd);
409
410		vchan_get_all_descriptors(&chan->vc, &head);
411	}
412
413	vchan_dma_desc_free_list(&chan->vc, &head);
414
415	return 0;
416}
417
418static void ls1x_dma_synchronize(struct dma_chan *dchan)
419{
420	vchan_synchronize(to_virt_chan(dchan));
421}
422
423static enum dma_status ls1x_dma_tx_status(struct dma_chan *dchan,
424					  dma_cookie_t cookie,
425					  struct dma_tx_state *state)
426{
427	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
428	struct virt_dma_desc *vd;
429	enum dma_status status;
430	size_t bytes = 0;
431
432	status = dma_cookie_status(dchan, cookie, state);
433	if (status == DMA_COMPLETE)
434		return status;
435
436	scoped_guard(spinlock_irqsave, &chan->vc.lock) {
437		vd = vchan_find_desc(&chan->vc, cookie);
438		if (vd) {
439			struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);
440			struct ls1x_dma_lli *lli;
441			dma_addr_t next_phys;
442
443			/* get the current lli */
444			if (ls1x_dma_query(chan, &chan->curr_lli->phys))
445				return status;
446
447			/* locate the current lli */
448			next_phys = chan->curr_lli->hw[LS1X_DMADESC_NEXT];
449			list_for_each_entry(lli, &desc->lli_list, node)
450				if (lli->hw[LS1X_DMADESC_NEXT] == next_phys)
451					break;
452
453			dev_dbg(chan2dev(dchan), "current lli_phys=%pad",
454				&lli->phys);
455
456			/* count the residues */
457			list_for_each_entry_from(lli, &desc->lli_list, node)
458				bytes += lli->hw[LS1X_DMADESC_LENGTH] *
459					 chan->bus_width;
460		}
461	}
462
463	dma_set_residue(state, bytes);
464
465	return status;
466}
467
468static void ls1x_dma_issue_pending(struct dma_chan *dchan)
469{
470	struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
471
472	guard(spinlock_irqsave)(&chan->vc.lock);
473
474	if (vchan_issue_pending(&chan->vc)) {
475		struct virt_dma_desc *vd = vchan_next_desc(&chan->vc);
476
477		if (vd) {
478			struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);
479			struct ls1x_dma_lli *lli;
480
481			lli = list_first_entry(&desc->lli_list,
482					       struct ls1x_dma_lli, node);
483			ls1x_dma_start(chan, &lli->phys);
484		}
485	}
486}
487
488static irqreturn_t ls1x_dma_irq_handler(int irq, void *data)
489{
490	struct ls1x_dma_chan *chan = data;
491	struct dma_chan *dchan = &chan->vc.chan;
492	struct device *dev = chan2dev(dchan);
493	struct virt_dma_desc *vd;
494
495	scoped_guard(spinlock, &chan->vc.lock) {
496		vd = vchan_next_desc(&chan->vc);
497		if (!vd) {
498			dev_warn(dev,
499				 "IRQ %d with no active desc on channel %d\n",
500				 irq, dchan->chan_id);
501			return IRQ_NONE;
502		}
503
504		if (chan->is_cyclic) {
505			vchan_cyclic_callback(vd);
506		} else {
507			list_del(&vd->node);
508			vchan_cookie_complete(vd);
509		}
510	}
511
512	dev_dbg(dev, "DMA IRQ %d on channel %d\n", irq, dchan->chan_id);
513
514	return IRQ_HANDLED;
515}
516
517static int ls1x_dma_chan_probe(struct platform_device *pdev,
518			       struct ls1x_dma *dma)
519{
520	void __iomem *reg_base;
521	int id;
522
523	reg_base = devm_platform_ioremap_resource(pdev, 0);
524	if (IS_ERR(reg_base))
525		return PTR_ERR(reg_base);
526
527	for (id = 0; id < dma->nr_chans; id++) {
528		struct ls1x_dma_chan *chan = &dma->chan[id];
529		char pdev_irqname[16];
530
531		snprintf(pdev_irqname, sizeof(pdev_irqname), "ch%d", id);
532		chan->irq = platform_get_irq_byname(pdev, pdev_irqname);
533		if (chan->irq < 0)
534			return dev_err_probe(&pdev->dev, chan->irq,
535					     "failed to get IRQ for ch%d\n",
536					     id);
537
538		chan->reg_base = reg_base;
539		chan->vc.desc_free = ls1x_dma_free_desc;
540		vchan_init(&chan->vc, &dma->ddev);
541	}
542
543	return 0;
544}
545
546static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
547{
548	int id;
549
550	for (id = 0; id < dma->nr_chans; id++) {
551		struct ls1x_dma_chan *chan = &dma->chan[id];
552
553		if (chan->vc.chan.device == &dma->ddev) {
554			list_del(&chan->vc.chan.device_node);
555			tasklet_kill(&chan->vc.task);
556		}
557	}
558}
559
560static int ls1x_dma_probe(struct platform_device *pdev)
561{
562	struct device *dev = &pdev->dev;
563	struct dma_device *ddev;
564	struct ls1x_dma *dma;
565	int ret;
566
567	ret = platform_irq_count(pdev);
568	if (ret <= 0 || ret > LS1X_DMA_MAX_CHANNELS)
569		return dev_err_probe(dev, -EINVAL,
570				     "Invalid number of IRQ channels: %d\n",
571				     ret);
572
573	dma = devm_kzalloc(dev, struct_size(dma, chan, ret), GFP_KERNEL);
574	if (!dma)
575		return -ENOMEM;
576	dma->nr_chans = ret;
577
578	/* initialize DMA device */
579	ddev = &dma->ddev;
580	ddev->dev = dev;
581	ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;
582	ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
583				BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
584				BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
585	ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
586				BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
587				BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
588	ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
589	ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
590	ddev->device_alloc_chan_resources = ls1x_dma_alloc_chan_resources;
591	ddev->device_free_chan_resources = ls1x_dma_free_chan_resources;
592	ddev->device_prep_slave_sg = ls1x_dma_prep_slave_sg;
593	ddev->device_prep_dma_cyclic = ls1x_dma_prep_dma_cyclic;
594	ddev->device_config = ls1x_dma_slave_config;
595	ddev->device_pause = ls1x_dma_pause;
596	ddev->device_resume = ls1x_dma_resume;
597	ddev->device_terminate_all = ls1x_dma_terminate_all;
598	ddev->device_synchronize = ls1x_dma_synchronize;
599	ddev->device_tx_status = ls1x_dma_tx_status;
600	ddev->device_issue_pending = ls1x_dma_issue_pending;
601	dma_cap_set(DMA_SLAVE, ddev->cap_mask);
602	INIT_LIST_HEAD(&ddev->channels);
603
604	/* initialize DMA channels */
605	ret = ls1x_dma_chan_probe(pdev, dma);
606	if (ret)
607		goto err;
608
609	ret = dmaenginem_async_device_register(ddev);
610	if (ret) {
611		dev_err(dev, "failed to register DMA device\n");
612		goto err;
613	}
614
615	ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id,
616					 ddev);
617	if (ret) {
618		dev_err(dev, "failed to register DMA controller\n");
619		goto err;
620	}
621
622	platform_set_drvdata(pdev, dma);
623	dev_info(dev, "Loongson1 DMA driver registered\n");
624
625	return 0;
626
627err:
628	ls1x_dma_chan_remove(dma);
629
630	return ret;
631}
632
633static void ls1x_dma_remove(struct platform_device *pdev)
634{
635	struct ls1x_dma *dma = platform_get_drvdata(pdev);
636
637	of_dma_controller_free(pdev->dev.of_node);
638	ls1x_dma_chan_remove(dma);
639}
640
641static const struct of_device_id ls1x_dma_match[] = {
642	{ .compatible = "loongson,ls1b-apbdma" },
643	{ /* sentinel */ }
644};
645MODULE_DEVICE_TABLE(of, ls1x_dma_match);
646
647static struct platform_driver ls1x_dma_driver = {
648	.probe = ls1x_dma_probe,
649	.remove = ls1x_dma_remove,
650	.driver = {
651		.name = KBUILD_MODNAME,
652		.of_match_table = ls1x_dma_match,
653	},
654};
655
656module_platform_driver(ls1x_dma_driver);
657
658MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
659MODULE_DESCRIPTION("Loongson-1 APB DMA Controller driver");
660MODULE_LICENSE("GPL");
~/ Read-only file preview.