162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * BCM2835 DMA engine support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Florian Meier <florian.meier@koalo.de> 662306a36Sopenharmony_ci * Copyright 2013 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Based on 962306a36Sopenharmony_ci * OMAP DMAengine support by Russell King 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * BCM2708 DMA Driver 1262306a36Sopenharmony_ci * Copyright (C) 2010 Broadcom 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Raspberry Pi PCM I2S ALSA Driver 1562306a36Sopenharmony_ci * Copyright (c) by Phil Poole 2013 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * MARVELL MMP Peripheral DMA Driver 1862306a36Sopenharmony_ci * Copyright 2012 Marvell International Ltd. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci#include <linux/dmaengine.h> 2162306a36Sopenharmony_ci#include <linux/dma-mapping.h> 2262306a36Sopenharmony_ci#include <linux/dmapool.h> 2362306a36Sopenharmony_ci#include <linux/err.h> 2462306a36Sopenharmony_ci#include <linux/init.h> 2562306a36Sopenharmony_ci#include <linux/interrupt.h> 2662306a36Sopenharmony_ci#include <linux/list.h> 2762306a36Sopenharmony_ci#include <linux/module.h> 2862306a36Sopenharmony_ci#include <linux/platform_device.h> 2962306a36Sopenharmony_ci#include <linux/slab.h> 3062306a36Sopenharmony_ci#include <linux/io.h> 3162306a36Sopenharmony_ci#include <linux/spinlock.h> 3262306a36Sopenharmony_ci#include <linux/of.h> 3362306a36Sopenharmony_ci#include <linux/of_dma.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include "virt-dma.h" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED 14 3862306a36Sopenharmony_ci#define BCM2835_DMA_CHAN_NAME_SIZE 8 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/** 4162306a36Sopenharmony_ci * struct bcm2835_dmadev - BCM2835 DMA controller 4262306a36Sopenharmony_ci * @ddev: DMA device 4362306a36Sopenharmony_ci * @base: base address of register map 4462306a36Sopenharmony_ci * @zero_page: bus address of zero page (to detect transactions copying from 4562306a36Sopenharmony_ci * zero page and avoid accessing memory if so) 4662306a36Sopenharmony_ci */ 4762306a36Sopenharmony_cistruct bcm2835_dmadev { 4862306a36Sopenharmony_ci struct dma_device ddev; 4962306a36Sopenharmony_ci void __iomem *base; 5062306a36Sopenharmony_ci dma_addr_t zero_page; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistruct bcm2835_dma_cb { 5462306a36Sopenharmony_ci uint32_t info; 5562306a36Sopenharmony_ci uint32_t src; 5662306a36Sopenharmony_ci uint32_t dst; 5762306a36Sopenharmony_ci uint32_t length; 5862306a36Sopenharmony_ci uint32_t stride; 5962306a36Sopenharmony_ci uint32_t next; 6062306a36Sopenharmony_ci uint32_t pad[2]; 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistruct bcm2835_cb_entry { 6462306a36Sopenharmony_ci struct bcm2835_dma_cb *cb; 6562306a36Sopenharmony_ci dma_addr_t paddr; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistruct bcm2835_chan { 6962306a36Sopenharmony_ci struct virt_dma_chan vc; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci struct dma_slave_config cfg; 7262306a36Sopenharmony_ci unsigned int dreq; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci int ch; 7562306a36Sopenharmony_ci struct bcm2835_desc *desc; 7662306a36Sopenharmony_ci struct dma_pool *cb_pool; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci void __iomem *chan_base; 7962306a36Sopenharmony_ci int irq_number; 8062306a36Sopenharmony_ci unsigned int irq_flags; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci bool is_lite_channel; 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistruct bcm2835_desc { 8662306a36Sopenharmony_ci struct bcm2835_chan *c; 8762306a36Sopenharmony_ci struct virt_dma_desc vd; 8862306a36Sopenharmony_ci enum dma_transfer_direction dir; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci unsigned int frames; 9162306a36Sopenharmony_ci size_t size; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci bool cyclic; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci struct bcm2835_cb_entry cb_list[]; 9662306a36Sopenharmony_ci}; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci#define BCM2835_DMA_CS 0x00 9962306a36Sopenharmony_ci#define BCM2835_DMA_ADDR 0x04 10062306a36Sopenharmony_ci#define BCM2835_DMA_TI 0x08 10162306a36Sopenharmony_ci#define BCM2835_DMA_SOURCE_AD 0x0c 10262306a36Sopenharmony_ci#define BCM2835_DMA_DEST_AD 0x10 10362306a36Sopenharmony_ci#define BCM2835_DMA_LEN 0x14 10462306a36Sopenharmony_ci#define BCM2835_DMA_STRIDE 0x18 10562306a36Sopenharmony_ci#define BCM2835_DMA_NEXTCB 0x1c 10662306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG 0x20 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* DMA CS Control and Status bits */ 10962306a36Sopenharmony_ci#define BCM2835_DMA_ACTIVE BIT(0) /* activate the DMA */ 11062306a36Sopenharmony_ci#define BCM2835_DMA_END BIT(1) /* current CB has ended */ 11162306a36Sopenharmony_ci#define BCM2835_DMA_INT BIT(2) /* interrupt status */ 11262306a36Sopenharmony_ci#define BCM2835_DMA_DREQ BIT(3) /* DREQ state */ 11362306a36Sopenharmony_ci#define BCM2835_DMA_ISPAUSED BIT(4) /* Pause requested or not active */ 11462306a36Sopenharmony_ci#define BCM2835_DMA_ISHELD BIT(5) /* Is held by DREQ flow control */ 11562306a36Sopenharmony_ci#define BCM2835_DMA_WAITING_FOR_WRITES BIT(6) /* waiting for last 11662306a36Sopenharmony_ci * AXI-write to ack 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci#define BCM2835_DMA_ERR BIT(8) 11962306a36Sopenharmony_ci#define BCM2835_DMA_PRIORITY(x) ((x & 15) << 16) /* AXI priority */ 12062306a36Sopenharmony_ci#define BCM2835_DMA_PANIC_PRIORITY(x) ((x & 15) << 20) /* panic priority */ 12162306a36Sopenharmony_ci/* current value of TI.BCM2835_DMA_WAIT_RESP */ 12262306a36Sopenharmony_ci#define BCM2835_DMA_WAIT_FOR_WRITES BIT(28) 12362306a36Sopenharmony_ci#define BCM2835_DMA_DIS_DEBUG BIT(29) /* disable debug pause signal */ 12462306a36Sopenharmony_ci#define BCM2835_DMA_ABORT BIT(30) /* Stop current CB, go to next, WO */ 12562306a36Sopenharmony_ci#define BCM2835_DMA_RESET BIT(31) /* WO, self clearing */ 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci/* Transfer information bits - also bcm2835_cb.info field */ 12862306a36Sopenharmony_ci#define BCM2835_DMA_INT_EN BIT(0) 12962306a36Sopenharmony_ci#define BCM2835_DMA_TDMODE BIT(1) /* 2D-Mode */ 13062306a36Sopenharmony_ci#define BCM2835_DMA_WAIT_RESP BIT(3) /* wait for AXI-write to be acked */ 13162306a36Sopenharmony_ci#define BCM2835_DMA_D_INC BIT(4) 13262306a36Sopenharmony_ci#define BCM2835_DMA_D_WIDTH BIT(5) /* 128bit writes if set */ 13362306a36Sopenharmony_ci#define BCM2835_DMA_D_DREQ BIT(6) /* enable DREQ for destination */ 13462306a36Sopenharmony_ci#define BCM2835_DMA_D_IGNORE BIT(7) /* ignore destination writes */ 13562306a36Sopenharmony_ci#define BCM2835_DMA_S_INC BIT(8) 13662306a36Sopenharmony_ci#define BCM2835_DMA_S_WIDTH BIT(9) /* 128bit writes if set */ 13762306a36Sopenharmony_ci#define BCM2835_DMA_S_DREQ BIT(10) /* enable SREQ for source */ 13862306a36Sopenharmony_ci#define BCM2835_DMA_S_IGNORE BIT(11) /* ignore source reads - read 0 */ 13962306a36Sopenharmony_ci#define BCM2835_DMA_BURST_LENGTH(x) ((x & 15) << 12) 14062306a36Sopenharmony_ci#define BCM2835_DMA_PER_MAP(x) ((x & 31) << 16) /* REQ source */ 14162306a36Sopenharmony_ci#define BCM2835_DMA_WAIT(x) ((x & 31) << 21) /* add DMA-wait cycles */ 14262306a36Sopenharmony_ci#define BCM2835_DMA_NO_WIDE_BURSTS BIT(26) /* no 2 beat write bursts */ 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/* debug register bits */ 14562306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_LAST_NOT_SET_ERR BIT(0) 14662306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_FIFO_ERR BIT(1) 14762306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_READ_ERR BIT(2) 14862306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_OUTSTANDING_WRITES_SHIFT 4 14962306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_OUTSTANDING_WRITES_BITS 4 15062306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_ID_SHIFT 16 15162306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_ID_BITS 9 15262306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_STATE_SHIFT 16 15362306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_STATE_BITS 9 15462306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_VERSION_SHIFT 25 15562306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_VERSION_BITS 3 15662306a36Sopenharmony_ci#define BCM2835_DMA_DEBUG_LITE BIT(28) 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/* shared registers for all dma channels */ 15962306a36Sopenharmony_ci#define BCM2835_DMA_INT_STATUS 0xfe0 16062306a36Sopenharmony_ci#define BCM2835_DMA_ENABLE 0xff0 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci#define BCM2835_DMA_DATA_TYPE_S8 1 16362306a36Sopenharmony_ci#define BCM2835_DMA_DATA_TYPE_S16 2 16462306a36Sopenharmony_ci#define BCM2835_DMA_DATA_TYPE_S32 4 16562306a36Sopenharmony_ci#define BCM2835_DMA_DATA_TYPE_S128 16 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/* Valid only for channels 0 - 14, 15 has its own base address */ 16862306a36Sopenharmony_ci#define BCM2835_DMA_CHAN(n) ((n) << 8) /* Base address */ 16962306a36Sopenharmony_ci#define BCM2835_DMA_CHANIO(base, n) ((base) + BCM2835_DMA_CHAN(n)) 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci/* the max dma length for different channels */ 17262306a36Sopenharmony_ci#define MAX_DMA_LEN SZ_1G 17362306a36Sopenharmony_ci#define MAX_LITE_DMA_LEN (SZ_64K - 4) 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic inline size_t bcm2835_dma_max_frame_length(struct bcm2835_chan *c) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci /* lite and normal channels have different max frame length */ 17862306a36Sopenharmony_ci return c->is_lite_channel ? MAX_LITE_DMA_LEN : MAX_DMA_LEN; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/* how many frames of max_len size do we need to transfer len bytes */ 18262306a36Sopenharmony_cistatic inline size_t bcm2835_dma_frames_for_length(size_t len, 18362306a36Sopenharmony_ci size_t max_len) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci return DIV_ROUND_UP(len, max_len); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic inline struct bcm2835_dmadev *to_bcm2835_dma_dev(struct dma_device *d) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci return container_of(d, struct bcm2835_dmadev, ddev); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic inline struct bcm2835_chan *to_bcm2835_dma_chan(struct dma_chan *c) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci return container_of(c, struct bcm2835_chan, vc.chan); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic inline struct bcm2835_desc *to_bcm2835_dma_desc( 19962306a36Sopenharmony_ci struct dma_async_tx_descriptor *t) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci return container_of(t, struct bcm2835_desc, vd.tx); 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic void bcm2835_dma_free_cb_chain(struct bcm2835_desc *desc) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci size_t i; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci for (i = 0; i < desc->frames; i++) 20962306a36Sopenharmony_ci dma_pool_free(desc->c->cb_pool, desc->cb_list[i].cb, 21062306a36Sopenharmony_ci desc->cb_list[i].paddr); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci kfree(desc); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void bcm2835_dma_desc_free(struct virt_dma_desc *vd) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci bcm2835_dma_free_cb_chain( 21862306a36Sopenharmony_ci container_of(vd, struct bcm2835_desc, vd)); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic void bcm2835_dma_create_cb_set_length( 22262306a36Sopenharmony_ci struct bcm2835_chan *chan, 22362306a36Sopenharmony_ci struct bcm2835_dma_cb *control_block, 22462306a36Sopenharmony_ci size_t len, 22562306a36Sopenharmony_ci size_t period_len, 22662306a36Sopenharmony_ci size_t *total_len, 22762306a36Sopenharmony_ci u32 finalextrainfo) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci size_t max_len = bcm2835_dma_max_frame_length(chan); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* set the length taking lite-channel limitations into account */ 23262306a36Sopenharmony_ci control_block->length = min_t(u32, len, max_len); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* finished if we have no period_length */ 23562306a36Sopenharmony_ci if (!period_len) 23662306a36Sopenharmony_ci return; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* 23962306a36Sopenharmony_ci * period_len means: that we need to generate 24062306a36Sopenharmony_ci * transfers that are terminating at every 24162306a36Sopenharmony_ci * multiple of period_len - this is typically 24262306a36Sopenharmony_ci * used to set the interrupt flag in info 24362306a36Sopenharmony_ci * which is required during cyclic transfers 24462306a36Sopenharmony_ci */ 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* have we filled in period_length yet? */ 24762306a36Sopenharmony_ci if (*total_len + control_block->length < period_len) { 24862306a36Sopenharmony_ci /* update number of bytes in this period so far */ 24962306a36Sopenharmony_ci *total_len += control_block->length; 25062306a36Sopenharmony_ci return; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci /* calculate the length that remains to reach period_length */ 25462306a36Sopenharmony_ci control_block->length = period_len - *total_len; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* reset total_length for next period */ 25762306a36Sopenharmony_ci *total_len = 0; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* add extrainfo bits in info */ 26062306a36Sopenharmony_ci control_block->info |= finalextrainfo; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic inline size_t bcm2835_dma_count_frames_for_sg( 26462306a36Sopenharmony_ci struct bcm2835_chan *c, 26562306a36Sopenharmony_ci struct scatterlist *sgl, 26662306a36Sopenharmony_ci unsigned int sg_len) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci size_t frames = 0; 26962306a36Sopenharmony_ci struct scatterlist *sgent; 27062306a36Sopenharmony_ci unsigned int i; 27162306a36Sopenharmony_ci size_t plength = bcm2835_dma_max_frame_length(c); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci for_each_sg(sgl, sgent, sg_len, i) 27462306a36Sopenharmony_ci frames += bcm2835_dma_frames_for_length( 27562306a36Sopenharmony_ci sg_dma_len(sgent), plength); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return frames; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci/** 28162306a36Sopenharmony_ci * bcm2835_dma_create_cb_chain - create a control block and fills data in 28262306a36Sopenharmony_ci * 28362306a36Sopenharmony_ci * @chan: the @dma_chan for which we run this 28462306a36Sopenharmony_ci * @direction: the direction in which we transfer 28562306a36Sopenharmony_ci * @cyclic: it is a cyclic transfer 28662306a36Sopenharmony_ci * @info: the default info bits to apply per controlblock 28762306a36Sopenharmony_ci * @frames: number of controlblocks to allocate 28862306a36Sopenharmony_ci * @src: the src address to assign (if the S_INC bit is set 28962306a36Sopenharmony_ci * in @info, then it gets incremented) 29062306a36Sopenharmony_ci * @dst: the dst address to assign (if the D_INC bit is set 29162306a36Sopenharmony_ci * in @info, then it gets incremented) 29262306a36Sopenharmony_ci * @buf_len: the full buffer length (may also be 0) 29362306a36Sopenharmony_ci * @period_len: the period length when to apply @finalextrainfo 29462306a36Sopenharmony_ci * in addition to the last transfer 29562306a36Sopenharmony_ci * this will also break some control-blocks early 29662306a36Sopenharmony_ci * @finalextrainfo: additional bits in last controlblock 29762306a36Sopenharmony_ci * (or when period_len is reached in case of cyclic) 29862306a36Sopenharmony_ci * @gfp: the GFP flag to use for allocation 29962306a36Sopenharmony_ci */ 30062306a36Sopenharmony_cistatic struct bcm2835_desc *bcm2835_dma_create_cb_chain( 30162306a36Sopenharmony_ci struct dma_chan *chan, enum dma_transfer_direction direction, 30262306a36Sopenharmony_ci bool cyclic, u32 info, u32 finalextrainfo, size_t frames, 30362306a36Sopenharmony_ci dma_addr_t src, dma_addr_t dst, size_t buf_len, 30462306a36Sopenharmony_ci size_t period_len, gfp_t gfp) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 30762306a36Sopenharmony_ci size_t len = buf_len, total_len; 30862306a36Sopenharmony_ci size_t frame; 30962306a36Sopenharmony_ci struct bcm2835_desc *d; 31062306a36Sopenharmony_ci struct bcm2835_cb_entry *cb_entry; 31162306a36Sopenharmony_ci struct bcm2835_dma_cb *control_block; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (!frames) 31462306a36Sopenharmony_ci return NULL; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* allocate and setup the descriptor. */ 31762306a36Sopenharmony_ci d = kzalloc(struct_size(d, cb_list, frames), gfp); 31862306a36Sopenharmony_ci if (!d) 31962306a36Sopenharmony_ci return NULL; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci d->c = c; 32262306a36Sopenharmony_ci d->dir = direction; 32362306a36Sopenharmony_ci d->cyclic = cyclic; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* 32662306a36Sopenharmony_ci * Iterate over all frames, create a control block 32762306a36Sopenharmony_ci * for each frame and link them together. 32862306a36Sopenharmony_ci */ 32962306a36Sopenharmony_ci for (frame = 0, total_len = 0; frame < frames; d->frames++, frame++) { 33062306a36Sopenharmony_ci cb_entry = &d->cb_list[frame]; 33162306a36Sopenharmony_ci cb_entry->cb = dma_pool_alloc(c->cb_pool, gfp, 33262306a36Sopenharmony_ci &cb_entry->paddr); 33362306a36Sopenharmony_ci if (!cb_entry->cb) 33462306a36Sopenharmony_ci goto error_cb; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* fill in the control block */ 33762306a36Sopenharmony_ci control_block = cb_entry->cb; 33862306a36Sopenharmony_ci control_block->info = info; 33962306a36Sopenharmony_ci control_block->src = src; 34062306a36Sopenharmony_ci control_block->dst = dst; 34162306a36Sopenharmony_ci control_block->stride = 0; 34262306a36Sopenharmony_ci control_block->next = 0; 34362306a36Sopenharmony_ci /* set up length in control_block if requested */ 34462306a36Sopenharmony_ci if (buf_len) { 34562306a36Sopenharmony_ci /* calculate length honoring period_length */ 34662306a36Sopenharmony_ci bcm2835_dma_create_cb_set_length( 34762306a36Sopenharmony_ci c, control_block, 34862306a36Sopenharmony_ci len, period_len, &total_len, 34962306a36Sopenharmony_ci cyclic ? finalextrainfo : 0); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* calculate new remaining length */ 35262306a36Sopenharmony_ci len -= control_block->length; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci /* link this the last controlblock */ 35662306a36Sopenharmony_ci if (frame) 35762306a36Sopenharmony_ci d->cb_list[frame - 1].cb->next = cb_entry->paddr; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* update src and dst and length */ 36062306a36Sopenharmony_ci if (src && (info & BCM2835_DMA_S_INC)) 36162306a36Sopenharmony_ci src += control_block->length; 36262306a36Sopenharmony_ci if (dst && (info & BCM2835_DMA_D_INC)) 36362306a36Sopenharmony_ci dst += control_block->length; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci /* Length of total transfer */ 36662306a36Sopenharmony_ci d->size += control_block->length; 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci /* the last frame requires extra flags */ 37062306a36Sopenharmony_ci d->cb_list[d->frames - 1].cb->info |= finalextrainfo; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci /* detect a size missmatch */ 37362306a36Sopenharmony_ci if (buf_len && (d->size != buf_len)) 37462306a36Sopenharmony_ci goto error_cb; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return d; 37762306a36Sopenharmony_cierror_cb: 37862306a36Sopenharmony_ci bcm2835_dma_free_cb_chain(d); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci return NULL; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_cistatic void bcm2835_dma_fill_cb_chain_with_sg( 38462306a36Sopenharmony_ci struct dma_chan *chan, 38562306a36Sopenharmony_ci enum dma_transfer_direction direction, 38662306a36Sopenharmony_ci struct bcm2835_cb_entry *cb, 38762306a36Sopenharmony_ci struct scatterlist *sgl, 38862306a36Sopenharmony_ci unsigned int sg_len) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 39162306a36Sopenharmony_ci size_t len, max_len; 39262306a36Sopenharmony_ci unsigned int i; 39362306a36Sopenharmony_ci dma_addr_t addr; 39462306a36Sopenharmony_ci struct scatterlist *sgent; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci max_len = bcm2835_dma_max_frame_length(c); 39762306a36Sopenharmony_ci for_each_sg(sgl, sgent, sg_len, i) { 39862306a36Sopenharmony_ci for (addr = sg_dma_address(sgent), len = sg_dma_len(sgent); 39962306a36Sopenharmony_ci len > 0; 40062306a36Sopenharmony_ci addr += cb->cb->length, len -= cb->cb->length, cb++) { 40162306a36Sopenharmony_ci if (direction == DMA_DEV_TO_MEM) 40262306a36Sopenharmony_ci cb->cb->dst = addr; 40362306a36Sopenharmony_ci else 40462306a36Sopenharmony_ci cb->cb->src = addr; 40562306a36Sopenharmony_ci cb->cb->length = min(len, max_len); 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_cistatic void bcm2835_dma_abort(struct bcm2835_chan *c) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci void __iomem *chan_base = c->chan_base; 41362306a36Sopenharmony_ci long int timeout = 10000; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci /* 41662306a36Sopenharmony_ci * A zero control block address means the channel is idle. 41762306a36Sopenharmony_ci * (The ACTIVE flag in the CS register is not a reliable indicator.) 41862306a36Sopenharmony_ci */ 41962306a36Sopenharmony_ci if (!readl(chan_base + BCM2835_DMA_ADDR)) 42062306a36Sopenharmony_ci return; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* Write 0 to the active bit - Pause the DMA */ 42362306a36Sopenharmony_ci writel(0, chan_base + BCM2835_DMA_CS); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* Wait for any current AXI transfer to complete */ 42662306a36Sopenharmony_ci while ((readl(chan_base + BCM2835_DMA_CS) & 42762306a36Sopenharmony_ci BCM2835_DMA_WAITING_FOR_WRITES) && --timeout) 42862306a36Sopenharmony_ci cpu_relax(); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci /* Peripheral might be stuck and fail to signal AXI write responses */ 43162306a36Sopenharmony_ci if (!timeout) 43262306a36Sopenharmony_ci dev_err(c->vc.chan.device->dev, 43362306a36Sopenharmony_ci "failed to complete outstanding writes\n"); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_cistatic void bcm2835_dma_start_desc(struct bcm2835_chan *c) 43962306a36Sopenharmony_ci{ 44062306a36Sopenharmony_ci struct virt_dma_desc *vd = vchan_next_desc(&c->vc); 44162306a36Sopenharmony_ci struct bcm2835_desc *d; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci if (!vd) { 44462306a36Sopenharmony_ci c->desc = NULL; 44562306a36Sopenharmony_ci return; 44662306a36Sopenharmony_ci } 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci list_del(&vd->node); 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci c->desc = d = to_bcm2835_dma_desc(&vd->tx); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci writel(d->cb_list[0].paddr, c->chan_base + BCM2835_DMA_ADDR); 45362306a36Sopenharmony_ci writel(BCM2835_DMA_ACTIVE, c->chan_base + BCM2835_DMA_CS); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic irqreturn_t bcm2835_dma_callback(int irq, void *data) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci struct bcm2835_chan *c = data; 45962306a36Sopenharmony_ci struct bcm2835_desc *d; 46062306a36Sopenharmony_ci unsigned long flags; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci /* check the shared interrupt */ 46362306a36Sopenharmony_ci if (c->irq_flags & IRQF_SHARED) { 46462306a36Sopenharmony_ci /* check if the interrupt is enabled */ 46562306a36Sopenharmony_ci flags = readl(c->chan_base + BCM2835_DMA_CS); 46662306a36Sopenharmony_ci /* if not set then we are not the reason for the irq */ 46762306a36Sopenharmony_ci if (!(flags & BCM2835_DMA_INT)) 46862306a36Sopenharmony_ci return IRQ_NONE; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci spin_lock_irqsave(&c->vc.lock, flags); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci /* 47462306a36Sopenharmony_ci * Clear the INT flag to receive further interrupts. Keep the channel 47562306a36Sopenharmony_ci * active in case the descriptor is cyclic or in case the client has 47662306a36Sopenharmony_ci * already terminated the descriptor and issued a new one. (May happen 47762306a36Sopenharmony_ci * if this IRQ handler is threaded.) If the channel is finished, it 47862306a36Sopenharmony_ci * will remain idle despite the ACTIVE flag being set. 47962306a36Sopenharmony_ci */ 48062306a36Sopenharmony_ci writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE, 48162306a36Sopenharmony_ci c->chan_base + BCM2835_DMA_CS); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci d = c->desc; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (d) { 48662306a36Sopenharmony_ci if (d->cyclic) { 48762306a36Sopenharmony_ci /* call the cyclic callback */ 48862306a36Sopenharmony_ci vchan_cyclic_callback(&d->vd); 48962306a36Sopenharmony_ci } else if (!readl(c->chan_base + BCM2835_DMA_ADDR)) { 49062306a36Sopenharmony_ci vchan_cookie_complete(&c->desc->vd); 49162306a36Sopenharmony_ci bcm2835_dma_start_desc(c); 49262306a36Sopenharmony_ci } 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci spin_unlock_irqrestore(&c->vc.lock, flags); 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci return IRQ_HANDLED; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic int bcm2835_dma_alloc_chan_resources(struct dma_chan *chan) 50162306a36Sopenharmony_ci{ 50262306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 50362306a36Sopenharmony_ci struct device *dev = c->vc.chan.device->dev; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci dev_dbg(dev, "Allocating DMA channel %d\n", c->ch); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci /* 50862306a36Sopenharmony_ci * Control blocks are 256 bit in length and must start at a 256 bit 50962306a36Sopenharmony_ci * (32 byte) aligned address (BCM2835 ARM Peripherals, sec. 4.2.1.1). 51062306a36Sopenharmony_ci */ 51162306a36Sopenharmony_ci c->cb_pool = dma_pool_create(dev_name(dev), dev, 51262306a36Sopenharmony_ci sizeof(struct bcm2835_dma_cb), 32, 0); 51362306a36Sopenharmony_ci if (!c->cb_pool) { 51462306a36Sopenharmony_ci dev_err(dev, "unable to allocate descriptor pool\n"); 51562306a36Sopenharmony_ci return -ENOMEM; 51662306a36Sopenharmony_ci } 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci return request_irq(c->irq_number, bcm2835_dma_callback, 51962306a36Sopenharmony_ci c->irq_flags, "DMA IRQ", c); 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cistatic void bcm2835_dma_free_chan_resources(struct dma_chan *chan) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci vchan_free_chan_resources(&c->vc); 52762306a36Sopenharmony_ci free_irq(c->irq_number, c); 52862306a36Sopenharmony_ci dma_pool_destroy(c->cb_pool); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci dev_dbg(c->vc.chan.device->dev, "Freeing DMA channel %u\n", c->ch); 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic size_t bcm2835_dma_desc_size(struct bcm2835_desc *d) 53462306a36Sopenharmony_ci{ 53562306a36Sopenharmony_ci return d->size; 53662306a36Sopenharmony_ci} 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic size_t bcm2835_dma_desc_size_pos(struct bcm2835_desc *d, dma_addr_t addr) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci unsigned int i; 54162306a36Sopenharmony_ci size_t size; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci for (size = i = 0; i < d->frames; i++) { 54462306a36Sopenharmony_ci struct bcm2835_dma_cb *control_block = d->cb_list[i].cb; 54562306a36Sopenharmony_ci size_t this_size = control_block->length; 54662306a36Sopenharmony_ci dma_addr_t dma; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci if (d->dir == DMA_DEV_TO_MEM) 54962306a36Sopenharmony_ci dma = control_block->dst; 55062306a36Sopenharmony_ci else 55162306a36Sopenharmony_ci dma = control_block->src; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci if (size) 55462306a36Sopenharmony_ci size += this_size; 55562306a36Sopenharmony_ci else if (addr >= dma && addr < dma + this_size) 55662306a36Sopenharmony_ci size += dma + this_size - addr; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci return size; 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cistatic enum dma_status bcm2835_dma_tx_status(struct dma_chan *chan, 56362306a36Sopenharmony_ci dma_cookie_t cookie, struct dma_tx_state *txstate) 56462306a36Sopenharmony_ci{ 56562306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 56662306a36Sopenharmony_ci struct virt_dma_desc *vd; 56762306a36Sopenharmony_ci enum dma_status ret; 56862306a36Sopenharmony_ci unsigned long flags; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci ret = dma_cookie_status(chan, cookie, txstate); 57162306a36Sopenharmony_ci if (ret == DMA_COMPLETE || !txstate) 57262306a36Sopenharmony_ci return ret; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci spin_lock_irqsave(&c->vc.lock, flags); 57562306a36Sopenharmony_ci vd = vchan_find_desc(&c->vc, cookie); 57662306a36Sopenharmony_ci if (vd) { 57762306a36Sopenharmony_ci txstate->residue = 57862306a36Sopenharmony_ci bcm2835_dma_desc_size(to_bcm2835_dma_desc(&vd->tx)); 57962306a36Sopenharmony_ci } else if (c->desc && c->desc->vd.tx.cookie == cookie) { 58062306a36Sopenharmony_ci struct bcm2835_desc *d = c->desc; 58162306a36Sopenharmony_ci dma_addr_t pos; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (d->dir == DMA_MEM_TO_DEV) 58462306a36Sopenharmony_ci pos = readl(c->chan_base + BCM2835_DMA_SOURCE_AD); 58562306a36Sopenharmony_ci else if (d->dir == DMA_DEV_TO_MEM) 58662306a36Sopenharmony_ci pos = readl(c->chan_base + BCM2835_DMA_DEST_AD); 58762306a36Sopenharmony_ci else 58862306a36Sopenharmony_ci pos = 0; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci txstate->residue = bcm2835_dma_desc_size_pos(d, pos); 59162306a36Sopenharmony_ci } else { 59262306a36Sopenharmony_ci txstate->residue = 0; 59362306a36Sopenharmony_ci } 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci spin_unlock_irqrestore(&c->vc.lock, flags); 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci return ret; 59862306a36Sopenharmony_ci} 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_cistatic void bcm2835_dma_issue_pending(struct dma_chan *chan) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 60362306a36Sopenharmony_ci unsigned long flags; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci spin_lock_irqsave(&c->vc.lock, flags); 60662306a36Sopenharmony_ci if (vchan_issue_pending(&c->vc) && !c->desc) 60762306a36Sopenharmony_ci bcm2835_dma_start_desc(c); 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci spin_unlock_irqrestore(&c->vc.lock, flags); 61062306a36Sopenharmony_ci} 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_memcpy( 61362306a36Sopenharmony_ci struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, 61462306a36Sopenharmony_ci size_t len, unsigned long flags) 61562306a36Sopenharmony_ci{ 61662306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 61762306a36Sopenharmony_ci struct bcm2835_desc *d; 61862306a36Sopenharmony_ci u32 info = BCM2835_DMA_D_INC | BCM2835_DMA_S_INC; 61962306a36Sopenharmony_ci u32 extra = BCM2835_DMA_INT_EN | BCM2835_DMA_WAIT_RESP; 62062306a36Sopenharmony_ci size_t max_len = bcm2835_dma_max_frame_length(c); 62162306a36Sopenharmony_ci size_t frames; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci /* if src, dst or len is not given return with an error */ 62462306a36Sopenharmony_ci if (!src || !dst || !len) 62562306a36Sopenharmony_ci return NULL; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci /* calculate number of frames */ 62862306a36Sopenharmony_ci frames = bcm2835_dma_frames_for_length(len, max_len); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci /* allocate the CB chain - this also fills in the pointers */ 63162306a36Sopenharmony_ci d = bcm2835_dma_create_cb_chain(chan, DMA_MEM_TO_MEM, false, 63262306a36Sopenharmony_ci info, extra, frames, 63362306a36Sopenharmony_ci src, dst, len, 0, GFP_KERNEL); 63462306a36Sopenharmony_ci if (!d) 63562306a36Sopenharmony_ci return NULL; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci return vchan_tx_prep(&c->vc, &d->vd, flags); 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg( 64162306a36Sopenharmony_ci struct dma_chan *chan, 64262306a36Sopenharmony_ci struct scatterlist *sgl, unsigned int sg_len, 64362306a36Sopenharmony_ci enum dma_transfer_direction direction, 64462306a36Sopenharmony_ci unsigned long flags, void *context) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 64762306a36Sopenharmony_ci struct bcm2835_desc *d; 64862306a36Sopenharmony_ci dma_addr_t src = 0, dst = 0; 64962306a36Sopenharmony_ci u32 info = BCM2835_DMA_WAIT_RESP; 65062306a36Sopenharmony_ci u32 extra = BCM2835_DMA_INT_EN; 65162306a36Sopenharmony_ci size_t frames; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci if (!is_slave_direction(direction)) { 65462306a36Sopenharmony_ci dev_err(chan->device->dev, 65562306a36Sopenharmony_ci "%s: bad direction?\n", __func__); 65662306a36Sopenharmony_ci return NULL; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci if (c->dreq != 0) 66062306a36Sopenharmony_ci info |= BCM2835_DMA_PER_MAP(c->dreq); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci if (direction == DMA_DEV_TO_MEM) { 66362306a36Sopenharmony_ci if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) 66462306a36Sopenharmony_ci return NULL; 66562306a36Sopenharmony_ci src = c->cfg.src_addr; 66662306a36Sopenharmony_ci info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC; 66762306a36Sopenharmony_ci } else { 66862306a36Sopenharmony_ci if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) 66962306a36Sopenharmony_ci return NULL; 67062306a36Sopenharmony_ci dst = c->cfg.dst_addr; 67162306a36Sopenharmony_ci info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC; 67262306a36Sopenharmony_ci } 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci /* count frames in sg list */ 67562306a36Sopenharmony_ci frames = bcm2835_dma_count_frames_for_sg(c, sgl, sg_len); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci /* allocate the CB chain */ 67862306a36Sopenharmony_ci d = bcm2835_dma_create_cb_chain(chan, direction, false, 67962306a36Sopenharmony_ci info, extra, 68062306a36Sopenharmony_ci frames, src, dst, 0, 0, 68162306a36Sopenharmony_ci GFP_NOWAIT); 68262306a36Sopenharmony_ci if (!d) 68362306a36Sopenharmony_ci return NULL; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci /* fill in frames with scatterlist pointers */ 68662306a36Sopenharmony_ci bcm2835_dma_fill_cb_chain_with_sg(chan, direction, d->cb_list, 68762306a36Sopenharmony_ci sgl, sg_len); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci return vchan_tx_prep(&c->vc, &d->vd, flags); 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic( 69362306a36Sopenharmony_ci struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, 69462306a36Sopenharmony_ci size_t period_len, enum dma_transfer_direction direction, 69562306a36Sopenharmony_ci unsigned long flags) 69662306a36Sopenharmony_ci{ 69762306a36Sopenharmony_ci struct bcm2835_dmadev *od = to_bcm2835_dma_dev(chan->device); 69862306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 69962306a36Sopenharmony_ci struct bcm2835_desc *d; 70062306a36Sopenharmony_ci dma_addr_t src, dst; 70162306a36Sopenharmony_ci u32 info = BCM2835_DMA_WAIT_RESP; 70262306a36Sopenharmony_ci u32 extra = 0; 70362306a36Sopenharmony_ci size_t max_len = bcm2835_dma_max_frame_length(c); 70462306a36Sopenharmony_ci size_t frames; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci /* Grab configuration */ 70762306a36Sopenharmony_ci if (!is_slave_direction(direction)) { 70862306a36Sopenharmony_ci dev_err(chan->device->dev, "%s: bad direction?\n", __func__); 70962306a36Sopenharmony_ci return NULL; 71062306a36Sopenharmony_ci } 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci if (!buf_len) { 71362306a36Sopenharmony_ci dev_err(chan->device->dev, 71462306a36Sopenharmony_ci "%s: bad buffer length (= 0)\n", __func__); 71562306a36Sopenharmony_ci return NULL; 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci if (flags & DMA_PREP_INTERRUPT) 71962306a36Sopenharmony_ci extra |= BCM2835_DMA_INT_EN; 72062306a36Sopenharmony_ci else 72162306a36Sopenharmony_ci period_len = buf_len; 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci /* 72462306a36Sopenharmony_ci * warn if buf_len is not a multiple of period_len - this may leed 72562306a36Sopenharmony_ci * to unexpected latencies for interrupts and thus audiable clicks 72662306a36Sopenharmony_ci */ 72762306a36Sopenharmony_ci if (buf_len % period_len) 72862306a36Sopenharmony_ci dev_warn_once(chan->device->dev, 72962306a36Sopenharmony_ci "%s: buffer_length (%zd) is not a multiple of period_len (%zd)\n", 73062306a36Sopenharmony_ci __func__, buf_len, period_len); 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Setup DREQ channel */ 73362306a36Sopenharmony_ci if (c->dreq != 0) 73462306a36Sopenharmony_ci info |= BCM2835_DMA_PER_MAP(c->dreq); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci if (direction == DMA_DEV_TO_MEM) { 73762306a36Sopenharmony_ci if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) 73862306a36Sopenharmony_ci return NULL; 73962306a36Sopenharmony_ci src = c->cfg.src_addr; 74062306a36Sopenharmony_ci dst = buf_addr; 74162306a36Sopenharmony_ci info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC; 74262306a36Sopenharmony_ci } else { 74362306a36Sopenharmony_ci if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES) 74462306a36Sopenharmony_ci return NULL; 74562306a36Sopenharmony_ci dst = c->cfg.dst_addr; 74662306a36Sopenharmony_ci src = buf_addr; 74762306a36Sopenharmony_ci info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC; 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci /* non-lite channels can write zeroes w/o accessing memory */ 75062306a36Sopenharmony_ci if (buf_addr == od->zero_page && !c->is_lite_channel) 75162306a36Sopenharmony_ci info |= BCM2835_DMA_S_IGNORE; 75262306a36Sopenharmony_ci } 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci /* calculate number of frames */ 75562306a36Sopenharmony_ci frames = /* number of periods */ 75662306a36Sopenharmony_ci DIV_ROUND_UP(buf_len, period_len) * 75762306a36Sopenharmony_ci /* number of frames per period */ 75862306a36Sopenharmony_ci bcm2835_dma_frames_for_length(period_len, max_len); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci /* 76162306a36Sopenharmony_ci * allocate the CB chain 76262306a36Sopenharmony_ci * note that we need to use GFP_NOWAIT, as the ALSA i2s dmaengine 76362306a36Sopenharmony_ci * implementation calls prep_dma_cyclic with interrupts disabled. 76462306a36Sopenharmony_ci */ 76562306a36Sopenharmony_ci d = bcm2835_dma_create_cb_chain(chan, direction, true, 76662306a36Sopenharmony_ci info, extra, 76762306a36Sopenharmony_ci frames, src, dst, buf_len, 76862306a36Sopenharmony_ci period_len, GFP_NOWAIT); 76962306a36Sopenharmony_ci if (!d) 77062306a36Sopenharmony_ci return NULL; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* wrap around into a loop */ 77362306a36Sopenharmony_ci d->cb_list[d->frames - 1].cb->next = d->cb_list[0].paddr; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci return vchan_tx_prep(&c->vc, &d->vd, flags); 77662306a36Sopenharmony_ci} 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_cistatic int bcm2835_dma_slave_config(struct dma_chan *chan, 77962306a36Sopenharmony_ci struct dma_slave_config *cfg) 78062306a36Sopenharmony_ci{ 78162306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci c->cfg = *cfg; 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci return 0; 78662306a36Sopenharmony_ci} 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_cistatic int bcm2835_dma_terminate_all(struct dma_chan *chan) 78962306a36Sopenharmony_ci{ 79062306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 79162306a36Sopenharmony_ci unsigned long flags; 79262306a36Sopenharmony_ci LIST_HEAD(head); 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci spin_lock_irqsave(&c->vc.lock, flags); 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci /* stop DMA activity */ 79762306a36Sopenharmony_ci if (c->desc) { 79862306a36Sopenharmony_ci vchan_terminate_vdesc(&c->desc->vd); 79962306a36Sopenharmony_ci c->desc = NULL; 80062306a36Sopenharmony_ci bcm2835_dma_abort(c); 80162306a36Sopenharmony_ci } 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci vchan_get_all_descriptors(&c->vc, &head); 80462306a36Sopenharmony_ci spin_unlock_irqrestore(&c->vc.lock, flags); 80562306a36Sopenharmony_ci vchan_dma_desc_free_list(&c->vc, &head); 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci return 0; 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_cistatic void bcm2835_dma_synchronize(struct dma_chan *chan) 81162306a36Sopenharmony_ci{ 81262306a36Sopenharmony_ci struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci vchan_synchronize(&c->vc); 81562306a36Sopenharmony_ci} 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_cistatic int bcm2835_dma_chan_init(struct bcm2835_dmadev *d, int chan_id, 81862306a36Sopenharmony_ci int irq, unsigned int irq_flags) 81962306a36Sopenharmony_ci{ 82062306a36Sopenharmony_ci struct bcm2835_chan *c; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci c = devm_kzalloc(d->ddev.dev, sizeof(*c), GFP_KERNEL); 82362306a36Sopenharmony_ci if (!c) 82462306a36Sopenharmony_ci return -ENOMEM; 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci c->vc.desc_free = bcm2835_dma_desc_free; 82762306a36Sopenharmony_ci vchan_init(&c->vc, &d->ddev); 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci c->chan_base = BCM2835_DMA_CHANIO(d->base, chan_id); 83062306a36Sopenharmony_ci c->ch = chan_id; 83162306a36Sopenharmony_ci c->irq_number = irq; 83262306a36Sopenharmony_ci c->irq_flags = irq_flags; 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci /* check in DEBUG register if this is a LITE channel */ 83562306a36Sopenharmony_ci if (readl(c->chan_base + BCM2835_DMA_DEBUG) & 83662306a36Sopenharmony_ci BCM2835_DMA_DEBUG_LITE) 83762306a36Sopenharmony_ci c->is_lite_channel = true; 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci return 0; 84062306a36Sopenharmony_ci} 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_cistatic void bcm2835_dma_free(struct bcm2835_dmadev *od) 84362306a36Sopenharmony_ci{ 84462306a36Sopenharmony_ci struct bcm2835_chan *c, *next; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci list_for_each_entry_safe(c, next, &od->ddev.channels, 84762306a36Sopenharmony_ci vc.chan.device_node) { 84862306a36Sopenharmony_ci list_del(&c->vc.chan.device_node); 84962306a36Sopenharmony_ci tasklet_kill(&c->vc.task); 85062306a36Sopenharmony_ci } 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE, 85362306a36Sopenharmony_ci DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); 85462306a36Sopenharmony_ci} 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_cistatic const struct of_device_id bcm2835_dma_of_match[] = { 85762306a36Sopenharmony_ci { .compatible = "brcm,bcm2835-dma", }, 85862306a36Sopenharmony_ci {}, 85962306a36Sopenharmony_ci}; 86062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, bcm2835_dma_of_match); 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_cistatic struct dma_chan *bcm2835_dma_xlate(struct of_phandle_args *spec, 86362306a36Sopenharmony_ci struct of_dma *ofdma) 86462306a36Sopenharmony_ci{ 86562306a36Sopenharmony_ci struct bcm2835_dmadev *d = ofdma->of_dma_data; 86662306a36Sopenharmony_ci struct dma_chan *chan; 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci chan = dma_get_any_slave_channel(&d->ddev); 86962306a36Sopenharmony_ci if (!chan) 87062306a36Sopenharmony_ci return NULL; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci /* Set DREQ from param */ 87362306a36Sopenharmony_ci to_bcm2835_dma_chan(chan)->dreq = spec->args[0]; 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci return chan; 87662306a36Sopenharmony_ci} 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_cistatic int bcm2835_dma_probe(struct platform_device *pdev) 87962306a36Sopenharmony_ci{ 88062306a36Sopenharmony_ci struct bcm2835_dmadev *od; 88162306a36Sopenharmony_ci void __iomem *base; 88262306a36Sopenharmony_ci int rc; 88362306a36Sopenharmony_ci int i, j; 88462306a36Sopenharmony_ci int irq[BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED + 1]; 88562306a36Sopenharmony_ci int irq_flags; 88662306a36Sopenharmony_ci uint32_t chans_available; 88762306a36Sopenharmony_ci char chan_name[BCM2835_DMA_CHAN_NAME_SIZE]; 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci if (!pdev->dev.dma_mask) 89062306a36Sopenharmony_ci pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); 89362306a36Sopenharmony_ci if (rc) { 89462306a36Sopenharmony_ci dev_err(&pdev->dev, "Unable to set DMA mask\n"); 89562306a36Sopenharmony_ci return rc; 89662306a36Sopenharmony_ci } 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci od = devm_kzalloc(&pdev->dev, sizeof(*od), GFP_KERNEL); 89962306a36Sopenharmony_ci if (!od) 90062306a36Sopenharmony_ci return -ENOMEM; 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_ci dma_set_max_seg_size(&pdev->dev, 0x3FFFFFFF); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 0); 90562306a36Sopenharmony_ci if (IS_ERR(base)) 90662306a36Sopenharmony_ci return PTR_ERR(base); 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci od->base = base; 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci dma_cap_set(DMA_SLAVE, od->ddev.cap_mask); 91162306a36Sopenharmony_ci dma_cap_set(DMA_PRIVATE, od->ddev.cap_mask); 91262306a36Sopenharmony_ci dma_cap_set(DMA_CYCLIC, od->ddev.cap_mask); 91362306a36Sopenharmony_ci dma_cap_set(DMA_MEMCPY, od->ddev.cap_mask); 91462306a36Sopenharmony_ci od->ddev.device_alloc_chan_resources = bcm2835_dma_alloc_chan_resources; 91562306a36Sopenharmony_ci od->ddev.device_free_chan_resources = bcm2835_dma_free_chan_resources; 91662306a36Sopenharmony_ci od->ddev.device_tx_status = bcm2835_dma_tx_status; 91762306a36Sopenharmony_ci od->ddev.device_issue_pending = bcm2835_dma_issue_pending; 91862306a36Sopenharmony_ci od->ddev.device_prep_dma_cyclic = bcm2835_dma_prep_dma_cyclic; 91962306a36Sopenharmony_ci od->ddev.device_prep_slave_sg = bcm2835_dma_prep_slave_sg; 92062306a36Sopenharmony_ci od->ddev.device_prep_dma_memcpy = bcm2835_dma_prep_dma_memcpy; 92162306a36Sopenharmony_ci od->ddev.device_config = bcm2835_dma_slave_config; 92262306a36Sopenharmony_ci od->ddev.device_terminate_all = bcm2835_dma_terminate_all; 92362306a36Sopenharmony_ci od->ddev.device_synchronize = bcm2835_dma_synchronize; 92462306a36Sopenharmony_ci od->ddev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); 92562306a36Sopenharmony_ci od->ddev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); 92662306a36Sopenharmony_ci od->ddev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV) | 92762306a36Sopenharmony_ci BIT(DMA_MEM_TO_MEM); 92862306a36Sopenharmony_ci od->ddev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; 92962306a36Sopenharmony_ci od->ddev.descriptor_reuse = true; 93062306a36Sopenharmony_ci od->ddev.dev = &pdev->dev; 93162306a36Sopenharmony_ci INIT_LIST_HEAD(&od->ddev.channels); 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci platform_set_drvdata(pdev, od); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci od->zero_page = dma_map_page_attrs(od->ddev.dev, ZERO_PAGE(0), 0, 93662306a36Sopenharmony_ci PAGE_SIZE, DMA_TO_DEVICE, 93762306a36Sopenharmony_ci DMA_ATTR_SKIP_CPU_SYNC); 93862306a36Sopenharmony_ci if (dma_mapping_error(od->ddev.dev, od->zero_page)) { 93962306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to map zero page\n"); 94062306a36Sopenharmony_ci return -ENOMEM; 94162306a36Sopenharmony_ci } 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci /* Request DMA channel mask from device tree */ 94462306a36Sopenharmony_ci if (of_property_read_u32(pdev->dev.of_node, 94562306a36Sopenharmony_ci "brcm,dma-channel-mask", 94662306a36Sopenharmony_ci &chans_available)) { 94762306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to get channel mask\n"); 94862306a36Sopenharmony_ci rc = -EINVAL; 94962306a36Sopenharmony_ci goto err_no_dma; 95062306a36Sopenharmony_ci } 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci /* get irqs for each channel that we support */ 95362306a36Sopenharmony_ci for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) { 95462306a36Sopenharmony_ci /* skip masked out channels */ 95562306a36Sopenharmony_ci if (!(chans_available & (1 << i))) { 95662306a36Sopenharmony_ci irq[i] = -1; 95762306a36Sopenharmony_ci continue; 95862306a36Sopenharmony_ci } 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci /* get the named irq */ 96162306a36Sopenharmony_ci snprintf(chan_name, sizeof(chan_name), "dma%i", i); 96262306a36Sopenharmony_ci irq[i] = platform_get_irq_byname(pdev, chan_name); 96362306a36Sopenharmony_ci if (irq[i] >= 0) 96462306a36Sopenharmony_ci continue; 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci /* legacy device tree case handling */ 96762306a36Sopenharmony_ci dev_warn_once(&pdev->dev, 96862306a36Sopenharmony_ci "missing interrupt-names property in device tree - legacy interpretation is used\n"); 96962306a36Sopenharmony_ci /* 97062306a36Sopenharmony_ci * in case of channel >= 11 97162306a36Sopenharmony_ci * use the 11th interrupt and that is shared 97262306a36Sopenharmony_ci */ 97362306a36Sopenharmony_ci irq[i] = platform_get_irq(pdev, i < 11 ? i : 11); 97462306a36Sopenharmony_ci } 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci /* get irqs for each channel */ 97762306a36Sopenharmony_ci for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) { 97862306a36Sopenharmony_ci /* skip channels without irq */ 97962306a36Sopenharmony_ci if (irq[i] < 0) 98062306a36Sopenharmony_ci continue; 98162306a36Sopenharmony_ci 98262306a36Sopenharmony_ci /* check if there are other channels that also use this irq */ 98362306a36Sopenharmony_ci irq_flags = 0; 98462306a36Sopenharmony_ci for (j = 0; j <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; j++) 98562306a36Sopenharmony_ci if ((i != j) && (irq[j] == irq[i])) { 98662306a36Sopenharmony_ci irq_flags = IRQF_SHARED; 98762306a36Sopenharmony_ci break; 98862306a36Sopenharmony_ci } 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_ci /* initialize the channel */ 99162306a36Sopenharmony_ci rc = bcm2835_dma_chan_init(od, i, irq[i], irq_flags); 99262306a36Sopenharmony_ci if (rc) 99362306a36Sopenharmony_ci goto err_no_dma; 99462306a36Sopenharmony_ci } 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci dev_dbg(&pdev->dev, "Initialized %i DMA channels\n", i); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci /* Device-tree DMA controller registration */ 99962306a36Sopenharmony_ci rc = of_dma_controller_register(pdev->dev.of_node, 100062306a36Sopenharmony_ci bcm2835_dma_xlate, od); 100162306a36Sopenharmony_ci if (rc) { 100262306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to register DMA controller\n"); 100362306a36Sopenharmony_ci goto err_no_dma; 100462306a36Sopenharmony_ci } 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci rc = dma_async_device_register(&od->ddev); 100762306a36Sopenharmony_ci if (rc) { 100862306a36Sopenharmony_ci dev_err(&pdev->dev, 100962306a36Sopenharmony_ci "Failed to register slave DMA engine device: %d\n", rc); 101062306a36Sopenharmony_ci goto err_no_dma; 101162306a36Sopenharmony_ci } 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ci dev_dbg(&pdev->dev, "Load BCM2835 DMA engine driver\n"); 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci return 0; 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_cierr_no_dma: 101862306a36Sopenharmony_ci bcm2835_dma_free(od); 101962306a36Sopenharmony_ci return rc; 102062306a36Sopenharmony_ci} 102162306a36Sopenharmony_ci 102262306a36Sopenharmony_cistatic int bcm2835_dma_remove(struct platform_device *pdev) 102362306a36Sopenharmony_ci{ 102462306a36Sopenharmony_ci struct bcm2835_dmadev *od = platform_get_drvdata(pdev); 102562306a36Sopenharmony_ci 102662306a36Sopenharmony_ci dma_async_device_unregister(&od->ddev); 102762306a36Sopenharmony_ci bcm2835_dma_free(od); 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci return 0; 103062306a36Sopenharmony_ci} 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_cistatic struct platform_driver bcm2835_dma_driver = { 103362306a36Sopenharmony_ci .probe = bcm2835_dma_probe, 103462306a36Sopenharmony_ci .remove = bcm2835_dma_remove, 103562306a36Sopenharmony_ci .driver = { 103662306a36Sopenharmony_ci .name = "bcm2835-dma", 103762306a36Sopenharmony_ci .of_match_table = of_match_ptr(bcm2835_dma_of_match), 103862306a36Sopenharmony_ci }, 103962306a36Sopenharmony_ci}; 104062306a36Sopenharmony_ci 104162306a36Sopenharmony_cimodule_platform_driver(bcm2835_dma_driver); 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_ciMODULE_ALIAS("platform:bcm2835-dma"); 104462306a36Sopenharmony_ciMODULE_DESCRIPTION("BCM2835 DMA engine driver"); 104562306a36Sopenharmony_ciMODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>"); 104662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1047