| /* |
| * arch/arm/plat-ambarella/generic/gdma.c |
| * |
| * Author: Louis Sun <lysun@ambarella.com> |
| * |
| * Copyright (C) 2004-2010, Ambarella, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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/init.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| |
| #include <mach/hardware.h> |
| |
| #define TRANSFER_2D_WIDTH (1<<12) /* 4096*/ |
| #define MAX_TRANSFER_2D_HEIGHT (1<<11) /* 2048*/ |
| #define MAX_TRANSFER_SIZE_2D_UNIT (TRANSFER_2D_WIDTH * MAX_TRANSFER_2D_HEIGHT) /* 8MB*/ |
| |
| #define TRANSFER_1D_WIDTH TRANSFER_2D_WIDTH |
| #define MAX_TRANSFER_SIZE_1D_UNIT TRANSFER_1D_WIDTH |
| |
| /* transfer 6 big blocks (although maximum is 8), because we may do another 1 small block and 1 line. total 8 Ops */ |
| #define MAX_TRANSFER_SIZE_ONCE (MAX_TRANSFER_SIZE_2D_UNIT * 6) /* 48 MB*/ |
| |
| #define MAX_OPS 8 |
| |
| static struct completion transfer_completion; |
| |
| |
| /* handle 8MB at one time */ |
| static inline int transfer_big_unit(u8 *dest_addr, u8 *src_addr, u32 size) |
| { |
| int row_count; |
| if (size > MAX_TRANSFER_SIZE_2D_UNIT) { |
| printk("transfer_unit size %d bigger than %d \n", |
| size, MAX_TRANSFER_SIZE_2D_UNIT); |
| return -1; |
| } |
| |
| row_count = size / TRANSFER_2D_WIDTH; |
| |
| /* copy rows by 2D copy */ |
| if (row_count > 0) { |
| amba_writel(GDMA_SRC_1_BASE_REG, (long)src_addr); |
| amba_writel(GDMA_SRC_1_PITCH_REG, TRANSFER_2D_WIDTH); |
| amba_writel(GDMA_DST_BASE_REG, (long)dest_addr); |
| amba_writel(GDMA_DST_PITCH_REG, TRANSFER_2D_WIDTH); |
| amba_writel(GDMA_WIDTH_REG, TRANSFER_2D_WIDTH - 1); |
| amba_writel(GDMA_HEIGHT_REG, row_count - 1); |
| |
| /* start 2D copy */ |
| amba_writel(GDMA_OPCODE_REG, 1); |
| } |
| return 0; |
| |
| } |
| |
| |
| /* use 1D copy to copy max 4KB each time */ |
| static inline int transfer_small_unit(u8 *dest_addr, u8 *src_addr, u32 size) |
| { |
| |
| if (size > TRANSFER_1D_WIDTH) { |
| printk("transfer_unit size %d bigger than %d \n", |
| size, TRANSFER_1D_WIDTH); |
| return -1; |
| } |
| |
| /* linear copy */ |
| amba_writel(GDMA_SRC_1_BASE_REG, (long)src_addr); |
| amba_writel(GDMA_DST_BASE_REG, (long)dest_addr); |
| amba_writel(GDMA_WIDTH_REG, TRANSFER_1D_WIDTH - 1); |
| |
| /* start linear copy */ |
| amba_writel(GDMA_OPCODE_REG, 0); |
| |
| return 0; |
| } |
| |
| |
| |
| /* this is async function, just fill dma registers and let it run*/ |
| static inline int transfer_once(u8 *dest_addr, u8 *src_addr, u32 size) |
| { |
| //total pending count must be no bigger than 8 |
| int big_count; |
| int rows_count; |
| int i; |
| u32 transferred_bytes = 0; |
| int remain_bytes ; |
| |
| if (size > MAX_TRANSFER_SIZE_ONCE) { |
| printk(" size too big %d for transfer once \n", size); |
| return -1; |
| } |
| |
| big_count = size/MAX_TRANSFER_SIZE_2D_UNIT; |
| //big pages (each is 8MB) |
| for (i = big_count ; i > 0; i--) { |
| transfer_big_unit(dest_addr + transferred_bytes, |
| src_addr + transferred_bytes, |
| MAX_TRANSFER_SIZE_2D_UNIT); |
| transferred_bytes += MAX_TRANSFER_SIZE_2D_UNIT; |
| } |
| remain_bytes = size - transferred_bytes; |
| |
| |
| //transfer rows (align to TRANSFER_2D_WIDTH) |
| rows_count = remain_bytes / TRANSFER_2D_WIDTH; |
| if (rows_count > 0) { |
| transfer_big_unit(dest_addr + transferred_bytes, |
| src_addr + transferred_bytes, |
| TRANSFER_2D_WIDTH * rows_count); |
| transferred_bytes += TRANSFER_2D_WIDTH * rows_count; |
| remain_bytes = size - transferred_bytes; |
| } |
| |
| if (remain_bytes > 0) { |
| transfer_small_unit(dest_addr + transferred_bytes, |
| src_addr + transferred_bytes, remain_bytes); |
| } |
| |
| return 0; |
| } |
| |
| /* this is synchronous function, will wait till transfer finishes */ |
| int dma_memcpy(u8 *dest_addr, u8 *src_addr, u32 size) |
| { |
| int remain_size = size; |
| int transferred_size = 0; |
| int current_transfer_size; |
| |
| while (remain_size > 0) { |
| if (remain_size > MAX_TRANSFER_SIZE_ONCE) { |
| remain_size -= MAX_TRANSFER_SIZE_ONCE; |
| current_transfer_size = MAX_TRANSFER_SIZE_ONCE; |
| } else { |
| current_transfer_size = remain_size; |
| remain_size = 0; |
| } |
| |
| transfer_once(dest_addr + transferred_size, |
| src_addr + transferred_size, current_transfer_size); |
| wait_for_completion(&transfer_completion); |
| transferred_size += current_transfer_size; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(dma_memcpy); |
| |
| |
| static irqreturn_t gdma_interrupt(int irq, void *dev_id) |
| { |
| int pending_ops; |
| pending_ops = amba_readl(GDMA_PENDING_OPS_REG); |
| |
| if (pending_ops == 0) { |
| /* if no following transfer */ |
| complete(&transfer_completion); |
| } else { |
| |
| } |
| return IRQ_HANDLED; |
| } |
| |
| |
| static int hw_init(void) |
| { |
| int errorCode; |
| /* request irq, no device id, no irq sharing */ |
| errorCode = request_irq(GDMA_IRQ, gdma_interrupt, |
| IRQF_TRIGGER_RISING, "gdma", 0); |
| |
| if (errorCode) { |
| printk("gdma irq request failed \n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* wait till transmit completes */ |
| static void wait_transmit_complete(void) |
| { |
| int pending_ops; |
| pending_ops = amba_readl(GDMA_PENDING_OPS_REG); |
| |
| while(pending_ops!= 0) { |
| mdelay(10); |
| } |
| } |
| |
| static int __init gdma_init(void) |
| { |
| /* hardware and irq init */ |
| if (hw_init() != 0) |
| return -1; |
| /* init completion */ |
| init_completion(&transfer_completion); |
| |
| return 0; |
| } |
| |
| |
| static void __exit gdma_exit(void) |
| { |
| wait_transmit_complete(); |
| } |
| |
| MODULE_AUTHOR("Louis Sun <lysun@ambarella.com>"); |
| MODULE_DESCRIPTION("GDMA driver on Ambarella A5s"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_VERSION("1.0"); |
| |
| module_init(gdma_init); |
| module_exit(gdma_exit); |
| |