summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRicardo Perez Olivares <richo@ti.com>2009-10-23 08:23:20 -0500
committerRicardo Perez Olivares <richo@ti.com>2009-10-23 11:49:21 -0500
commit607cc8b8d55966f3d34a01714bf6aae01a7f7ff6 (patch)
treef61f008ef952df40f535a05d42b19c4260b292f5
parentdb501f6eeffe6e6fd54ee22861ac197b09bd268b (diff)
OMAP DMA descriptor autoloading feature
Signed-off-by: Venkatraman S <svenkatr@ti.com>
-rw-r--r--arch/arm/plat-omap/dma.c303
-rwxr-xr-xarch/arm/plat-omap/include/mach/dma.h136
2 files changed, 439 insertions, 0 deletions
diff --git a/arch/arm/plat-omap/dma.c b/arch/arm/plat-omap/dma.c
index e3ac94f09006..d5760c777164 100644
--- a/arch/arm/plat-omap/dma.c
+++ b/arch/arm/plat-omap/dma.c
@@ -29,6 +29,7 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
+#include <linux/dma-mapping.h>
#include <asm/system.h>
#include <mach/hardware.h>
@@ -46,13 +47,42 @@ enum { DMA_CH_ALLOC_DONE, DMA_CH_PARAMS_SET_DONE, DMA_CH_STARTED,
enum { DMA_CHAIN_STARTED, DMA_CHAIN_NOTSTARTED };
#endif
+/* CDP Register bitmaps */
+#define DMA_LIST_CDP_DST_VALID (BIT(0))
+#define DMA_LIST_CDP_SRC_VALID (BIT(2))
+#define DMA_LIST_CDP_TYPE1 (BIT(4))
+#define DMA_LIST_CDP_TYPE2 (BIT(5))
+#define DMA_LIST_CDP_TYPE3 (BIT(4) | BIT(5))
+#define DMA_LIST_CDP_PAUSEMODE (BIT(7))
+#define DMA_LIST_CDP_LISTMODE (BIT(8))
+#define DMA_LIST_CDP_FASTMODE (BIT(10))
+/* CAPS register bitmaps */
+#define DMA_CAPS_SGLIST_SUPPORT (BIT(20))
+
+#define DMA_LIST_DESC_PAUSE (BIT(0))
+#define DMA_LIST_DESC_SRC_VALID (BIT(24))
+#define DMA_LIST_DESC_DST_VALID (BIT(26))
+#define DMA_LIST_DESC_BLK_END (BIT(28))
+
#define OMAP_DMA_ACTIVE 0x01
#define OMAP_DMA_CCR_EN (1 << 7)
#define OMAP2_DMA_CSR_CLEAR_MASK 0xffe
#define OMAP_FUNC_MUX_ARM_BASE (0xfffe1000 + 0xec)
+#define OMAP_DMA_INVALID_FRAME_COUNT (0xffff)
+#define OMAP_DMA_INVALID_ELEM_COUNT (0xffffff)
+#define OMAP_DMA_INVALID_DESCRIPTOR_POINTER (0xfffffffc)
static int enable_1510_mode;
+static int dma_caps0_status;
+
+struct omap_dma_list_config_params {
+ int chid;
+ int num_elem;
+ struct omap_dma_sglist_node *sghead;
+ int sgheadphy;
+ int pausenode;
+};
struct omap_dma_lch {
int next_lch;
@@ -72,6 +102,8 @@ struct omap_dma_lch {
int status;
#endif
+
+ void *list_config;
long flags;
};
@@ -209,6 +241,12 @@ static void clear_lch_regs(int lch)
__raw_writew(0, lch_base + i);
}
+static inline void omap_dma_list_set_ntype(struct omap_dma_sglist_node *node,
+ int value)
+{
+ node->num_of_elem |= ((value) << 29);
+}
+
void omap_set_dma_priority(int lch, int dst_port, int priority)
{
unsigned long reg;
@@ -1789,6 +1827,269 @@ EXPORT_SYMBOL(omap_get_dma_chain_src_pos);
#endif /* ifndef CONFIG_ARCH_OMAP1 */
/*----------------------------------------------------------------------------*/
+int omap_request_dma_sglist(int dev_id, const char *dev_name,
+ void (*callback) (int channel_id, u16 ch_status, void *data),
+ int *listid, int nelem, struct omap_dma_sglist_node **elems)
+{
+ struct omap_dma_list_config_params *lcfg;
+ struct omap_dma_sglist_node *desc;
+ int dma_lch;
+ int rc, i;
+
+ if (unlikely((dma_caps0_status & DMA_CAPS_SGLIST_SUPPORT) == 0)) {
+ printk(KERN_ERR "omap DMA: sglist feature not supported\n");
+ return -EPERM;
+ }
+ if (unlikely(nelem <= 2)) {
+ printk(KERN_ERR "omap DMA: Need >2 elements in the list\n");
+ return -EINVAL;
+ }
+ rc = omap_request_dma(dev_id, dev_name,
+ callback, NULL, &dma_lch);
+ if (rc < 0) {
+ printk(KERN_ERR "omap_dma_list: Request failed %d\n", rc);
+ return rc;
+ }
+ *listid = dma_lch;
+ dma_chan[dma_lch].state = DMA_CH_NOTSTARTED;
+ lcfg = kmalloc(sizeof(*lcfg), GFP_KERNEL);
+ if (NULL == lcfg)
+ goto error1;
+ dma_chan[dma_lch].list_config = lcfg;
+
+ lcfg->num_elem = nelem;
+
+ *elems = desc = lcfg->sghead = dma_alloc_coherent(NULL,
+ sizeof(*desc), &(lcfg->sgheadphy), 0);
+ if (NULL == desc)
+ goto error1;
+
+ for (i = 1; i < nelem; i++) {
+ desc->next = dma_alloc_coherent(NULL,
+ sizeof(*(desc->next)), &(desc->next_desc_add_ptr), 0);
+ if (NULL == desc->next)
+ goto error1;
+ desc = desc->next;
+ desc->next = NULL;
+ }
+ desc->next_desc_add_ptr = OMAP_DMA_INVALID_DESCRIPTOR_POINTER;
+
+ dma_write(0, CCDN(dma_lch)); /* Reset List index numbering */
+ /* Initialize frame and element counters to invalid values */
+ dma_write(OMAP_DMA_INVALID_FRAME_COUNT, CCFN(dma_lch));
+ dma_write(OMAP_DMA_INVALID_ELEM_COUNT, CCEN(dma_lch));
+ return 0;
+
+error1:
+ omap_release_dma_sglist(dma_lch);
+ return -ENOMEM;
+
+}
+EXPORT_SYMBOL(omap_request_dma_sglist);
+
+/* The client can choose to not preconfigure the DMA registers
+ * In fast mode,the DMA controller uses the first element in the list to
+ * program the registers first, and then starts the transfer
+ */
+
+int omap_set_dma_sglist_params(const int listid,
+ struct omap_dma_sglist_node *sghead,
+ struct omap_dma_channel_params *chparams)
+{
+ struct omap_dma_list_config_params *lcfg;
+ struct omap_dma_sglist_node *sgitcurr, *sgitprev;
+ int l = DMA_LIST_CDP_LISTMODE; /* Enable Linked list mode in CDP */
+
+ lcfg = dma_chan[listid].list_config;
+ if (lcfg->sghead != sghead) {
+ printk(KERN_ERR "Unknown config pointer passed\n");
+ return -EPERM;
+ }
+
+ if (NULL == chparams)
+ l |= DMA_LIST_CDP_FASTMODE;
+ else
+ omap_set_dma_params(listid, chparams);
+ /* The client can set the dma params and still use fast mode
+ * by using the set fast mode api
+ */
+ dma_write(l, CDP(listid));
+
+ sgitprev = sghead;
+ sgitcurr = sgitprev->next;
+
+ while (sgitcurr != NULL) {
+ switch (sgitcurr->desc_type) {
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE1:
+ omap_dma_list_set_ntype(sgitprev, 1);
+ break;
+
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2a:
+ /* intentional no break */
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2b:
+ omap_dma_list_set_ntype(sgitprev, 2);
+ break;
+
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3a:
+ /* intentional no break */
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3b:
+ omap_dma_list_set_ntype(sgitprev, 3);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (sgitcurr->flags & OMAP_DMA_LIST_SRC_VALID)
+ sgitprev->num_of_elem |= DMA_LIST_DESC_SRC_VALID;
+ if (sgitcurr->flags & OMAP_DMA_LIST_DST_VALID)
+ sgitprev->num_of_elem |= DMA_LIST_DESC_DST_VALID;
+ if (sgitcurr->flags & OMAP_DMA_LIST_NOTIFY_BLOCK_END)
+ sgitprev->num_of_elem |= DMA_LIST_DESC_BLK_END;
+
+ sgitprev = sgitcurr;
+ sgitcurr = sgitcurr->next;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(omap_set_dma_sglist_params);
+
+int omap_start_dma_sglist_transfers(const int listid, const int pauseafter)
+{
+ struct omap_dma_list_config_params *lcfg;
+ struct omap_dma_sglist_node *sgn;
+ unsigned int l, type_id;
+
+ lcfg = dma_chan[listid].list_config;
+ lcfg->pausenode = 0;
+ sgn = lcfg->sghead;
+ if (pauseafter > 0 && pauseafter <= lcfg->num_elem) {
+ for (l = 0; l < pauseafter; l++)
+ sgn = sgn->next;
+ sgn->next_desc_add_ptr |= DMA_LIST_DESC_PAUSE;
+ lcfg->pausenode = pauseafter;
+ }
+
+ /* Program the head descriptor's properties into CDP */
+ switch (lcfg->sghead->desc_type) {
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE1:
+ type_id = DMA_LIST_CDP_TYPE1;
+ break;
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2a:
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2b:
+ type_id = DMA_LIST_CDP_TYPE2;
+ break;
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3a:
+ case OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3b:
+ type_id = DMA_LIST_CDP_TYPE3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ l = dma_read(CDP(listid));
+ l |= type_id;
+ if (lcfg->sghead->flags & OMAP_DMA_LIST_SRC_VALID)
+ l |= DMA_LIST_CDP_SRC_VALID;
+ if (lcfg->sghead->flags & OMAP_DMA_LIST_DST_VALID)
+ l |= DMA_LIST_CDP_DST_VALID;
+
+ dma_write(l, CDP(listid));
+
+ dma_write((lcfg->sgheadphy), CNDP(listid));
+ printk(KERN_DEBUG "Start list transfer for list %x\n",
+ lcfg->sgheadphy);
+ omap_start_dma(listid);
+
+ return 0;
+}
+EXPORT_SYMBOL(omap_start_dma_sglist_transfers);
+
+int omap_resume_dma_sglist_transfers(const int listid, const int pauseafter)
+{
+ struct omap_dma_list_config_params *lcfg;
+ struct omap_dma_sglist_node *sgn;
+ int l;
+
+ lcfg = dma_chan[listid].list_config;
+ sgn = lcfg->sghead;
+ /* Clear the previous pause, if any */
+ lcfg->pausenode = 0;
+
+ if (pauseafter > 0 && pauseafter <= lcfg->num_elem) {
+ for (l = 0; l < pauseafter; l++)
+ sgn = sgn->next;
+ sgn->next_desc_add_ptr |= DMA_LIST_DESC_PAUSE;
+ lcfg->pausenode = pauseafter;
+ }
+
+ /* Clear pause bit in CDP */
+ l = dma_read(CDP(listid));
+ printk(KERN_DEBUG "Resuming after pause: CDP=%x\n", l);
+ l &= ~(DMA_LIST_CDP_PAUSEMODE);
+ dma_write(l, CDP(listid));
+ omap_start_dma(listid);
+ return 0;
+}
+EXPORT_SYMBOL(omap_resume_dma_sglist_transfers);
+
+int omap_release_dma_sglist(const int listid)
+{
+ struct omap_dma_list_config_params *lcfg;
+ struct omap_dma_sglist_node *sgn, *sgn2;
+
+ lcfg = dma_chan[listid].list_config;
+ sgn = lcfg->sghead;
+
+ if (NULL != sgn) {
+ sgn = sgn->next;
+ do {
+ sgn2 = sgn->next;
+ dma_free_coherent(NULL, sizeof(*sgn), sgn,
+ sgn->next_desc_add_ptr);
+ sgn = sgn2;
+ } while (sgn2 != NULL);
+
+ dma_free_coherent(NULL, sizeof(*(lcfg->sghead)),
+ lcfg->sghead, lcfg->sgheadphy);
+ }
+ if (NULL != dma_chan[listid].list_config)
+ kfree(dma_chan[listid].list_config);
+ dma_chan[listid].list_config = NULL;
+ omap_free_dma(listid);
+ return 0;
+}
+EXPORT_SYMBOL(omap_release_dma_sglist);
+
+int omap_get_completed_sglist_nodes(const int listid)
+{
+ int list_count;
+
+ list_count = dma_read(CCDN(listid));
+ return list_count & 0xffff; /* only 16 LSB bits are valid */
+}
+EXPORT_SYMBOL(omap_get_completed_sglist_nodes);
+
+int omap_dma_sglist_is_paused(const int listid)
+{
+ int list_state;
+
+ list_state = dma_read(CDP(listid));
+ return (list_state & DMA_LIST_CDP_PAUSEMODE) ? 1 : 0;
+}
+EXPORT_SYMBOL(omap_dma_sglist_is_paused);
+
+void omap_dma_set_sglist_fastmode(const int listid, const int fastmode)
+{
+ int l = dma_read(CDP(listid));
+
+ if (fastmode)
+ l |= DMA_LIST_CDP_FASTMODE;
+ else
+ l &= ~(DMA_LIST_CDP_FASTMODE);
+ dma_write(l, CDP(listid));
+}
+EXPORT_SYMBOL(omap_dma_set_sglist_fastmode);
+
#ifdef CONFIG_ARCH_OMAP1
@@ -2369,6 +2670,7 @@ static int __init omap_init_dma(void)
kfree(dma_chan);
return -ENOMEM;
}
+ dma_caps0_status = dma_read(CAPS_0);
}
if (cpu_is_omap15xx()) {
@@ -2420,6 +2722,7 @@ static int __init omap_init_dma(void)
omap_clear_dma(ch);
dma_chan[ch].dev_id = -1;
dma_chan[ch].next_lch = -1;
+ dma_chan[ch].list_config = NULL;
if (ch >= 6 && enable_1510_mode)
continue;
diff --git a/arch/arm/plat-omap/include/mach/dma.h b/arch/arm/plat-omap/include/mach/dma.h
index 7a5121522acd..afddb8256341 100755
--- a/arch/arm/plat-omap/include/mach/dma.h
+++ b/arch/arm/plat-omap/include/mach/dma.h
@@ -112,8 +112,12 @@
#define OMAP1_DMA_COLOR_U(n) (0x40 * (n) + 0x22)
#define OMAP1_DMA_CCR2(n) (0x40 * (n) + 0x24)
#define OMAP1_DMA_LCH_CTRL(n) (0x40 * (n) + 0x2a) /* not on 15xx */
+#define OMAP1_DMA_COLOR(n) 0
#define OMAP1_DMA_CCEN(n) 0
#define OMAP1_DMA_CCFN(n) 0
+#define OMAP1_DMA_CDP(n) 0
+#define OMAP1_DMA_CNDP(n) 0
+#define OMAP1_DMA_CCDN(n) 0
/* Channel specific registers only on omap2 */
#define OMAP_DMA4_CSSA(n) (0x60 * (n) + 0x9c)
@@ -131,6 +135,8 @@
#define OMAP1_DMA_IRQSTATUS_L0 0
#define OMAP1_DMA_IRQENABLE_L0 0
#define OMAP1_DMA_OCP_SYSCONFIG 0
+#define OMAP1_DMA_CAPS_0 0
+
#define OMAP_DMA4_HW_ID 0
#define OMAP_DMA4_CAPS_0_L 0
#define OMAP_DMA4_CAPS_0_U 0
@@ -552,6 +558,83 @@ struct omap_dma_channel_params {
#endif
};
+struct omap_dma_sglist_type1_params {
+ u32 src_addr;
+ u32 dst_addr;
+ u16 cfn_fn;
+ u16 cicr;
+ u16 dst_elem_idx;
+ u16 src_elem_idx;
+ u32 dst_frame_idx_or_pkt_size;
+ u32 src_frame_idx_or_pkt_size;
+ u32 color;
+ u32 csdp;
+ u32 clnk_ctrl;
+ u32 ccr;
+};
+
+struct omap_dma_sglist_type2a_params {
+ u32 src_addr;
+ u32 dst_addr;
+ u16 cfn_fn;
+ u16 cicr;
+ u16 dst_elem_idx;
+ u16 src_elem_idx;
+ u32 dst_frame_idx_or_pkt_size;
+ u32 src_frame_idx_or_pkt_size;
+};
+
+struct omap_dma_sglist_type2b_params {
+ u32 src_or_dest_addr;
+ u16 cfn_fn;
+ u16 cicr;
+ u16 dst_elem_idx;
+ u16 src_elem_idx;
+ u32 dst_frame_idx_or_pkt_size;
+ u32 src_frame_idx_or_pkt_size;
+};
+
+struct omap_dma_sglist_type3a_params {
+ u32 src_addr;
+ u32 dst_addr;
+};
+
+struct omap_dma_sglist_type3b_params {
+ u32 src_or_dest_addr;
+};
+
+enum omap_dma_sglist_descriptor_select {
+ OMAP_DMA_SGLIST_DESCRIPTOR_TYPE1,
+ OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2a,
+ OMAP_DMA_SGLIST_DESCRIPTOR_TYPE2b,
+ OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3a,
+ OMAP_DMA_SGLIST_DESCRIPTOR_TYPE3b,
+};
+
+union omap_dma_sglist_node_type{
+ struct omap_dma_sglist_type1_params t1;
+ struct omap_dma_sglist_type2a_params t2a;
+ struct omap_dma_sglist_type2b_params t2b;
+ struct omap_dma_sglist_type3a_params t3a;
+ struct omap_dma_sglist_type3b_params t3b;
+};
+
+struct omap_dma_sglist_node {
+
+ /* Common elements for all descriptors */
+ u32 next_desc_add_ptr;
+ u32 num_of_elem;
+ /* Type specific elements */
+ union omap_dma_sglist_node_type sg_node;
+ /* Control fields */
+ int flags;
+ /* Fields that can be set in flags variable */
+ #define OMAP_DMA_LIST_SRC_VALID (1)
+ #define OMAP_DMA_LIST_DST_VALID (2)
+ #define OMAP_DMA_LIST_NOTIFY_BLOCK_END (4)
+ u32 desc_type;
+ struct omap_dma_sglist_node *next;
+};
extern void omap_set_dma_priority(int lch, int dst_port, int priority);
extern int omap_request_dma(int dev_id, const char *dev_name,
@@ -631,6 +714,59 @@ extern int omap_modify_dma_chain_params(int chain_id,
struct omap_dma_channel_params params);
extern int omap_dma_chain_status(int chain_id);
#endif
+/* omap_request_dma_sglist:
+ * Request to setup a DMA channel to transfer in linked list mode of nelem
+ * elements. The memory for the list will be allocated and returned in
+ * elems structure
+ */
+extern int omap_request_dma_sglist(int dev_id, const char *dev_name,
+ void (*callback) (int channel_id, u16 ch_status, void *data),
+ int *listid, int nelem, struct omap_dma_sglist_node **elems);
+/* omap_set_dma_sglist_params
+ * Provide the configuration parameters for the sglist channel
+ * sghead should contain a fully populated list of nelems
+ * which completely describe the transfer. chparams, if not NULL, will
+ * set the appropriate parameters directly into the DMA register.
+ * If chparams is NULL, fastmode will be enabled automatically
+ */
+extern int omap_set_dma_sglist_params(const int listid,
+ struct omap_dma_sglist_node *sghead,
+ struct omap_dma_channel_params *chparams);
+/* omap_start_dma_sglist_transfers
+ * Starts the linked list based DMA transfer for the specified listid
+ * If no pause is required, -1 is to be set in pauseafter.
+ * Else, the transfer will suspend after pauseafter elements.
+ */
+extern int omap_start_dma_sglist_transfers(const int listid,
+ const int pauseafter);
+/* omap_resume_dma_sglist_transfers
+ * Resumes the previously paused transfer.
+ * Can be again set to pause at pauseafter node of the linked list
+ * The index is absolute (from the head of the list)
+ */
+extern int omap_resume_dma_sglist_transfers(const int listid,
+ const int pauseafter);
+/* omap_release_dma_sglist
+ * Releases the list based DMA channel and the associated list descriptors
+ */
+extern int omap_release_dma_sglist(const int listid);
+/* omap_get_completed_sglist_nodes
+ * Returns the number of completed elements in the linked list
+ * The value is transient if the API is invoked for an ongoing transfer
+ */
+int omap_get_completed_sglist_nodes(const int listid);
+/* omap_dma_sglist_is_paused
+ * Returns non zero if the linked list is currently in pause state
+ */
+int omap_dma_sglist_is_paused(const int listid);
+/* omap_dma_set_sglist_fastmode
+ * Set or clear the fastmode status of the transfer
+ * In fastmode, DMA register settings are updated from the first element
+ * of the linked list, before initiating the tranfer.
+ * In non-fastmode, the first element is used only after completing the
+ * transfer as already configured in the registers
+ */
+void omap_dma_set_sglist_fastmode(const int listid, const int fastmode);
/* LCD DMA functions */
extern int omap_request_lcd_dma(void (*callback)(u16 status, void *data),