blob: 61ce3bbd754bff3a03114b6dd087be8d78f41d7a [file] [log] [blame]
/*
* Copyright (C) 2011 Broadcom Corporation
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/brcmstb/brcmstb.h>
#ifdef BCHP_MEM_DMA_0_CTRL
struct brcm_mem_dma_descr {
u32 read_addr;
u32 write_addr;
u32 word[6];
};
#define DESCR_WORD2 0
#define DESCR_WORD3 1
#define DESCR_WORD4 2
#define BRCM_MEM_DMA_TIMEOUT (0) /* no timeout */
#define BRCM_MEM_DMA_DEBUG (0)
static int brcm_mem_dma_prepare_transfer(struct brcm_mem_transfer *xfer);
static int brcm_mem_dma_complete_transfer(struct brcm_mem_transfer *xfer);
static void brcm_mem_dma_start_transfer(void);
#if BRCM_MEM_DMA_DEBUG
#define DBG printk
static void brcm_mem_dma_dump_regs(const char *title)
{
printk(KERN_DEBUG "=== MEM DMA registers ===\n");
printk(KERN_DEBUG
"FIRST_DESC = 0x%08x CTRL = 0x%08x WAKE_CTRL = 0x%08x\n",
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_FIRST_DESC),
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_CTRL),
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_WAKE_CTRL));
printk(KERN_DEBUG
"STATUS = 0x%08x CUR_DESC = 0x%08x CUR_BYTE = 0x%08x\n",
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_STATUS),
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_CUR_DESC),
(unsigned int)BDEV_RD(BCHP_MEM_DMA_0_CUR_BYTE));
}
static void brcm_mem_dma_dump_descr(struct brcm_mem_dma_descr *d, int num)
{
int index = 0, last = 0;
printk(KERN_DEBUG "=== MEM DMA descriptors [%p] ===\n", d);
do {
last = d->word[DESCR_WORD2] &
BCHP_MEM_DMA_DESC_WORD2_LAST_MASK;
printk(KERN_DEBUG "[%02d]: s = 0x%08x d = 0x%08x "
"w2 = 0x%08x w3 = 0x%08x w4 = 0x%08x\n",
index++, d->read_addr, d->write_addr,
d->word[0], d->word[1], d->word[2]);
d++;
if (!--num)
break;
} while (!last);
}
#else
#define DBG(...) do { } while (0)
static inline void brcm_mem_dma_dump_regs(const char *title) {}
static inline void brcm_mem_dma_dump_descr(struct brcm_mem_dma_descr *d,
int num) {}
#endif
/***********************************************************************
* Low-level M2M utilities
***********************************************************************/
static inline void brcm_mem_dma_set_raddr(struct brcm_mem_dma_descr *descr,
u32 addr)
{
descr->read_addr = addr;
}
static inline void brcm_mem_dma_set_waddr(struct brcm_mem_dma_descr *descr,
u32 addr)
{
descr->write_addr = addr;
}
/***********************************************************************
* Enable/disable completion interrupt for descriptor
***********************************************************************/
static inline void brcm_mem_dma_interrupt_enable(
struct brcm_mem_dma_descr *descr, int enable)
{
if (enable)
descr->word[DESCR_WORD2] |=
BCHP_MEM_DMA_DESC_WORD2_INTR_ENABLE_MASK;
else
descr->word[DESCR_WORD2] &=
~BCHP_MEM_DMA_DESC_WORD2_INTR_ENABLE_MASK;
}
/***********************************************************************
* Set transfer size for descriptor
***********************************************************************/
static inline void brcm_mem_dma_set_xfer_size(struct brcm_mem_dma_descr *descr,
u32 len)
{
len &= BCHP_MEM_DMA_DESC_WORD2_TRANSFER_SIZE_MASK;
descr->word[DESCR_WORD2] &=
~BCHP_MEM_DMA_DESC_WORD2_TRANSFER_SIZE_MASK;
descr->word[DESCR_WORD2] |= len;
}
/***********************************************************************
* Link to the next descriptor
* next - virtual address of the descriptor to link with
***********************************************************************/
static inline void brcm_mem_dma_link_next(struct brcm_mem_dma_descr *descr,
struct brcm_mem_dma_descr *next)
{
u32 pa_next = 0;
if (next) {
pa_next = virt_to_phys(next) &
BCHP_MEM_DMA_DESC_WORD3_NEXT_DESC_ADDR_MASK;
descr->word[DESCR_WORD2] &= ~BCHP_MEM_DMA_DESC_WORD2_LAST_MASK;
} else
descr->word[DESCR_WORD2] |= BCHP_MEM_DMA_DESC_WORD2_LAST_MASK;
descr->word[DESCR_WORD3] &=
~BCHP_MEM_DMA_DESC_WORD3_NEXT_DESC_ADDR_MASK;
descr->word[DESCR_WORD3] |= pa_next;
}
/***********************************************************************
* Configure SCRAM
* TODO: encoder/decoder init bit ???
***********************************************************************/
static inline void brcm_mem_dma_set_scram(struct brcm_mem_dma_descr *descr,
int enable, int start, int end, int mode, u8 slot, int init)
{
u32 word4 = 0;
word4 |= enable ? BCHP_MEM_DMA_DESC_WORD4_SG_ENABLE_MASK : 0;
word4 |= start ? BCHP_MEM_DMA_DESC_WORD4_SG_SCRAM_START_MASK : 0;
word4 |= end ? BCHP_MEM_DMA_DESC_WORD4_SG_SCRAM_END_MASK : 0;
word4 |= (mode << BCHP_MEM_DMA_DESC_WORD4_MODE_SEL_SHIFT) &
BCHP_MEM_DMA_DESC_WORD4_MODE_SEL_MASK;
word4 |= (slot * 6) & BCHP_MEM_DMA_DESC_WORD4_KEY_SELECT_MASK;
if (mode && init)
word4 |= BCHP_MEM_DMA_DESC_WORD4_ENC_DEC_INIT_MASK;
descr->word[DESCR_WORD4] = word4;
}
/***********************************************************************
* Poll for transfer completion
***********************************************************************/
static int brcm_mem_dma_wait_for_ready(int timeout)
{
while (BDEV_RD_F(MEM_DMA_0_STATUS, DMA_STATUS) == 1) {
if (timeout > 0)
if (!--timeout)
return -1;
mdelay(1);
}
return 0;
}
static int brcm_mem_dma_num_descriptors(struct brcm_mem_transfer *xfer)
{
int xfer_cnt = 0;
do {
xfer_cnt++;
} while ((xfer = xfer->next));
return xfer_cnt;
}
/***********************************************************************
* Generate descriptor according to transfer request
* Returns 0 on success, -1 on incorrect request parameters
***********************************************************************/
static int brcm_mem_dma_gen_descriptor(struct brcm_mem_transfer *xfer,
struct brcm_mem_dma_descr *d, int index, int total)
{
int last = index == total - 1;
/* sanity checks */
if (xfer->len > 0x1000000) {
printk(KERN_ERR "MEM DMA "
"does not support transfers bigger than 16 MByte\n");
return -1;
}
if ((xfer->pa_dst > xfer->pa_src) &&
(xfer->pa_dst < (xfer->pa_src + xfer->len))) {
printk(KERN_ERR "MEM DMA "
"does not support overlapping read-write regions\n");
return -1;
}
/* set up mem-dma descriptor */
brcm_mem_dma_set_raddr(d, xfer->pa_src);
brcm_mem_dma_set_waddr(d, xfer->pa_dst);
brcm_mem_dma_set_xfer_size(d, xfer->len);
brcm_mem_dma_set_scram(d, 1, index == 0, last,
xfer->mode, xfer->key, index == 0);
brcm_mem_dma_link_next(d, last ? NULL : d + 1);
return 0;
}
/***********************************************************************
* If caller remapped the buffers, it set pa_src and pa_dst fields
* of transfer request. If these fields are not set, we need to remap
* them here.
* To properly unmap the buffers after the transfer, set bit flags
* in the transfer if we are doing remapping. Caller should never touch
* these flags.
***********************************************************************/
static int brcm_mem_dma_map_buffers(struct brcm_mem_transfer *xfer)
{
if (!xfer->len)
return -1;
if (!xfer->pa_src && !xfer->pa_dst && (xfer->src == xfer->dst)) {
xfer->pa_dst = xfer->pa_src =
dma_map_single(NULL, xfer->src, xfer->len,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(NULL, xfer->pa_dst))
return -1;
xfer->src_dst_remapped = 1;
}
if (!xfer->pa_src) {
xfer->pa_src = dma_map_single(NULL, xfer->src, xfer->len,
DMA_TO_DEVICE);
if (dma_mapping_error(NULL, xfer->pa_src))
return -1;
xfer->src_remapped = 1;
}
if (!xfer->pa_dst) {
xfer->pa_dst = dma_map_single(NULL, xfer->dst, xfer->len,
DMA_FROM_DEVICE);
if (dma_mapping_error(NULL, xfer->pa_dst))
return -1;
xfer->dst_remapped = 1;
}
return 0;
}
/***********************************************************************
* Unmap the buffers if we mapped them before
***********************************************************************/
static void brcm_mem_dma_unmap_buffers(struct brcm_mem_transfer *xfer)
{
if (xfer->src_dst_remapped) {
dma_unmap_single(NULL, xfer->pa_src, xfer->len,
DMA_BIDIRECTIONAL);
return;
}
if (xfer->src_remapped) {
dma_unmap_single(NULL, xfer->pa_src, xfer->len,
DMA_TO_DEVICE);
}
if (xfer->dst_remapped) {
dma_unmap_single(NULL, xfer->pa_dst, xfer->len,
DMA_FROM_DEVICE);
}
}
/***********************************************************************
* Map the descriptors and write the first descriptor address to the
* M2M register. Do not initiate transfer yet.
***********************************************************************/
static dma_addr_t brcm_mem_dma_prepare_descriptors(
struct brcm_mem_dma_descr *d, int size)
{
dma_addr_t pa_d;
pa_d = dma_map_single(NULL, d, size, DMA_TO_DEVICE);
brcm_mem_dma_wait_for_ready(0);
BDEV_WR_RB(BCHP_MEM_DMA_0_FIRST_DESC, pa_d);
return pa_d;
}
/***********************************************************************
* Unmap the descriptor after completion
***********************************************************************/
static void brcm_mem_dma_free_descriptors(dma_addr_t pa, int size)
{
brcm_mem_dma_wait_for_ready(0);
dma_unmap_single(NULL, pa, size, DMA_TO_DEVICE);
}
static struct brcm_mem_dma_descr *cur_descr;
static int cur_total_size;
static dma_addr_t cur_pa_d;
/***********************************************************************
* _brcm_mem_dma_start_transfer
* Initiate M2M transfer. If sync is set, wait for completion with timeout.
***********************************************************************/
static void _brcm_mem_dma_start_transfer(int on, int sync)
{
/* clear RUN bit and wait for IDLE */
BDEV_WR_F_RB(MEM_DMA_0_CTRL, RUN, 0);
brcm_mem_dma_wait_for_ready(BRCM_MEM_DMA_TIMEOUT);
if (on) {
BDEV_WR_F_RB(MEM_DMA_0_CTRL, RUN, 1);
if (sync)
brcm_mem_dma_wait_for_ready(BRCM_MEM_DMA_TIMEOUT);
}
}
/***********************************************************************
* brcm_mem_dma_start_transfer
* Initiate M2M transfer
***********************************************************************/
static void brcm_mem_dma_start_transfer(void)
{
DBG("%s:%d\n", __func__, __LINE__);
_brcm_mem_dma_start_transfer(1, 1);
}
/***********************************************************************
* brcm_mem_dma_prepare_transfer
* Generates mem dma descriptors, flushes them to memory
* and sets up M2M registers, but _DOES NOT_ initiate the transfer.
* Use brcm_mem_dma_start_transfer() to start transfer/encoding
* This allows S3 standby code to be sure code does not attempt to change
* memory content after it has been encoded
***********************************************************************/
static int brcm_mem_dma_prepare_transfer(struct brcm_mem_transfer *xfer)
{
int xfer_cnt = brcm_mem_dma_num_descriptors(xfer);
int total_size = xfer_cnt * sizeof(struct brcm_mem_dma_descr);
int index = 0;
struct brcm_mem_transfer *save_xfer = xfer;
struct brcm_mem_dma_descr *d =
kzalloc(total_size, GFP_ATOMIC), *cur_d;
DBG("%s:%d\n", __func__, __LINE__);
WARN_ON(cur_descr);
if (unlikely(!d))
return -1;
cur_descr = cur_d = d;
do {
if (brcm_mem_dma_map_buffers(xfer)) {
printk(KERN_ERR "%s: cannot map buffers", __func__);
goto _error;
}
if (brcm_mem_dma_gen_descriptor(xfer, cur_d,
index++, xfer_cnt)) {
printk(KERN_ERR "%s: cannot generate descr", __func__);
goto _error;
}
xfer = xfer->next;
cur_d++;
} while (xfer);
brcm_mem_dma_dump_descr(d, xfer_cnt);
/* flush the descriptors */
cur_pa_d = brcm_mem_dma_prepare_descriptors(d, total_size);
cur_total_size = total_size;
return 0;
_error:
brcm_mem_dma_complete_transfer(save_xfer);
return -1;
}
/***********************************************************************
* brcm_mem_dma_complete_transfer
* Must be called after M2M transfer was complete
* Unmaps everything that was mapped in brcm_mem_dma_prepare_transfer,
* frees up all memory
***********************************************************************/
static int brcm_mem_dma_complete_transfer(struct brcm_mem_transfer *xfer)
{
WARN_ON(cur_descr == NULL);
WARN_ON(!cur_pa_d);
WARN_ON(!cur_total_size);
DBG("%s:%d\n", __func__, __LINE__);
/* clear RUN bit and wait for IDLE */
_brcm_mem_dma_start_transfer(0, 1);
brcm_mem_dma_free_descriptors(cur_pa_d, cur_total_size);
do {
brcm_mem_dma_unmap_buffers(xfer);
xfer = xfer->next;
} while (xfer);
kfree(cur_descr);
cur_descr = NULL;
cur_total_size = 0;
cur_pa_d = 0;
return 0;
}
/***********************************************************************
* brcm_mem_dma_simple_transfer
* Initiates a single M2M tranfser according to transfer request
***********************************************************************/
int brcm_mem_dma_simple_transfer(struct brcm_mem_transfer *xfer)
{
int retval = 0;
struct brcm_mem_dma_descr *d =
kzalloc(sizeof(struct brcm_mem_dma_descr), GFP_ATOMIC);
DBG("%s:%d\n", __func__, __LINE__);
WARN_ON(brcm_mem_dma_num_descriptors(xfer) != 1);
if (d) {
u32 pa_d;
/* remap the buffers unless the caller has done that */
if (brcm_mem_dma_map_buffers(xfer)) {
retval = -1;
goto _error;
}
/* set up mem-dma descriptor */
if (!brcm_mem_dma_gen_descriptor(xfer, d, 0, 1)) {
brcm_mem_dma_dump_descr(d, 1);
/* flush the descriptor */
pa_d = brcm_mem_dma_prepare_descriptors(d,
sizeof(struct brcm_mem_dma_descr));
_brcm_mem_dma_start_transfer(1, 1);
brcm_mem_dma_dump_regs(__func__);
_brcm_mem_dma_start_transfer(0, 0);
brcm_mem_dma_free_descriptors(pa_d,
sizeof(struct brcm_mem_dma_descr));
}
brcm_mem_dma_unmap_buffers(xfer);
_error:
kfree(d);
return retval;
}
return -1;
}
/***********************************************************************
* brcm_mem_dma_transfer_sg
* Scatter-gather version of M2M tranfser
***********************************************************************/
int brcm_mem_dma_transfer_sg(struct brcm_mem_transfer *xfer)
{
printk(KERN_WARNING "%s: NOT IMPLEMENTED YET\n", __func__);
return -1;
}
/***********************************************************************
* brcm_mem_dma_transfer
* Initiates a M2M tranfser according to transfer request. Chained transfer
* will generate several descriptors.
***********************************************************************/
int brcm_mem_dma_transfer(struct brcm_mem_transfer *xfer)
{
struct brcm_mem_dma_descr *d;
int index = 0, retval = 0;
struct brcm_mem_dma_descr *cur_d;
int xfer_cnt = brcm_mem_dma_num_descriptors(xfer), total_size;
struct brcm_mem_transfer *xfer_save = xfer;
dma_addr_t pa_d;
DBG("%s:%d\n", __func__, __LINE__);
total_size = xfer_cnt * sizeof(struct brcm_mem_dma_descr);
d = kzalloc(total_size, GFP_ATOMIC);
if (!d)
return -1;
cur_d = d;
do {
/* remap the buffers unless the caller has done that */
if (brcm_mem_dma_map_buffers(xfer)) {
retval = -1;
goto _done;
}
if (brcm_mem_dma_gen_descriptor(xfer, cur_d,
index++, xfer_cnt)) {
retval = -1;
goto _done;
}
xfer = xfer->next;
cur_d++;
} while (xfer);
brcm_mem_dma_dump_descr(d, xfer_cnt);
/* flush the descriptors */
pa_d = brcm_mem_dma_prepare_descriptors(d, total_size);
brcm_mem_dma_dump_regs(__func__);
_brcm_mem_dma_start_transfer(1, 1);
_brcm_mem_dma_start_transfer(0, 0);
brcm_mem_dma_free_descriptors(pa_d, total_size);
_done:
xfer = xfer_save;
do {
brcm_mem_dma_unmap_buffers(xfer);
xfer = xfer->next;
} while (xfer);
kfree(d);
return retval;
}
#else
static int brcm_mem_dma_prepare_transfer(struct brcm_mem_transfer *xfer)
{
return -1;
}
static int brcm_mem_dma_complete_transfer(struct brcm_mem_transfer *xfer)
{
return -1;
}
static void brcm_mem_dma_start_transfer(void) {}
int brcm_mem_dma_simple_transfer(struct brcm_mem_transfer *xfer)
{
return -1;
}
int brcm_mem_dma_transfer(struct brcm_mem_transfer *xfer)
{
return -1;
}
#endif /* BCHP_MEM_DMA_0_CTRL */
static struct brcm_dram_encoder_ops m2m_ops = {
.prepare = brcm_mem_dma_prepare_transfer,
.start = brcm_mem_dma_start_transfer,
.complete = brcm_mem_dma_complete_transfer,
};
static int brcm_mem_dma_init(void)
{
/* TODO: request interrupt */
/* register encoder with power management subsystem */
brcm_pm_set_dram_encoder(&m2m_ops);
return 0;
}
late_initcall(brcm_mem_dma_init);