samd21: Fix up DMA driver
[fw/altos] / src / samd21 / ao_dma_samd21.c
1 /*
2  * Copyright © 2019 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18
19 #include <ao.h>
20 #include <ao_dma_samd21.h>
21
22 static struct samd21_dmac_desc samd21_dmac_desc[SAMD21_DMAC_NCHAN]  __attribute__((aligned(16)));
23 static struct samd21_dmac_desc samd21_dmac_wrb[SAMD21_DMAC_NCHAN]  __attribute__((aligned(16)));
24
25 static struct {
26         void (*callback)(uint8_t id, void *closure);
27         void *closure;
28 } dmac_callback[SAMD21_DMAC_NCHAN];
29
30 void
31 samd21_dmac_isr(void)
32 {
33         uint16_t        intpend = samd21_dmac.intpend;
34
35         if (intpend & 0x0700) {
36                 uint8_t id = (intpend >> SAMD21_DMAC_INTPEND_ID) & SAMD21_DMAC_INTPEND_ID_MASK;
37                 samd21_dmac.intpend = intpend;
38                 if (intpend & (1 << SAMD21_DMAC_INTPEND_TCMPL)) {
39                         if (dmac_callback[id].callback)
40                                 (*dmac_callback[id].callback)(id, dmac_callback[id].closure);
41                 }
42         }
43 }
44
45 void
46 ao_dma_dump(char *where)
47 {
48         printf("DMA %s ctrl %04x intpend %04x intstatus %04lx\n",
49                where,
50                samd21_dmac.ctrl,
51                samd21_dmac.intpend,
52                samd21_dmac.intstatus);
53         fflush(stdout);
54         printf(" busych %04lx pendch %04lx active %08lx chctrla %02x\n",
55                samd21_dmac.busych,
56                samd21_dmac.pendch,
57                samd21_dmac.active,
58                samd21_dmac.chctrla);
59         fflush(stdout);
60         printf(" chctrlb %08lx chintflag %02x chstatus %02x\n",
61                samd21_dmac.chctrlb,
62                samd21_dmac.chintflag,
63                samd21_dmac.chstatus);
64         fflush(stdout);
65         printf(" btctrl %04x btcnt %04x srcaddr %08lx dstaddr %08lx descaddr %08lx\n",
66                samd21_dmac_desc[0].btctrl,
67                samd21_dmac_desc[0].btcnt,
68                samd21_dmac_desc[0].srcaddr,
69                samd21_dmac_desc[0].dstaddr,
70                samd21_dmac_desc[0].descaddr);
71         fflush(stdout);
72 }
73
74 void
75 _ao_dma_start_transfer(uint8_t          id,
76                        const void       *src,
77                        void             *dst,
78                        uint16_t         count,
79                        uint32_t         chctrlb,
80                        uint16_t         btctrl,
81                        void             (*callback)(uint8_t id, void *closure),
82                        void             *closure)
83 {
84         /* Set up the callback */
85         dmac_callback[id].closure = closure;
86         dmac_callback[id].callback = callback;
87
88         /* Set up the descriptor */
89         samd21_dmac_desc[id].btctrl = btctrl;
90         samd21_dmac_desc[id].btcnt = count;
91         samd21_dmac_desc[id].srcaddr = (uint32_t) src;
92         samd21_dmac_desc[id].dstaddr = (uint32_t) dst;
93         samd21_dmac_desc[id].descaddr = 0;
94
95         /* Configure the channel and enable it */
96         samd21_dmac.chid = id;
97         samd21_dmac.chctrlb = chctrlb;
98         samd21_dmac.chctrla = (1 << SAMD21_DMAC_CHCTRLA_ENABLE);
99 }
100
101 void
102 _ao_dma_done_transfer(uint8_t id)
103 {
104         /* Disable channel */
105         samd21_dmac.chid = id;
106         samd21_dmac.chctrla = 0;
107 }
108
109 void
110 ao_dma_init(void)
111 {
112         uint8_t ch;
113
114         /* Enable DMAC clocks */
115         samd21_pm.ahbmask |= (1 << SAMD21_PM_AHBMASK_DMAC);
116         samd21_pm.apbbmask |= (1 << SAMD21_PM_APBBMASK_DMAC);
117
118 #if 1
119         /* Enable HPB clocks so we can talk to peripherals */
120         samd21_pm.ahbmask |= ((1 << SAMD21_PM_AHBMASK_HPB0) |
121                               (1 << SAMD21_PM_AHBMASK_HPB1) |
122                               (1 << SAMD21_PM_AHBMASK_HPB2));
123 #endif
124
125         samd21_pm.apbamask |= (1 << SAMD21_PM_APBAMASK_PAC0);
126         samd21_pm.apbbmask |= (1 << SAMD21_PM_APBBMASK_PAC1);
127         samd21_pm.apbcmask |= (1 << SAMD21_PM_APBCMASK_PAC2);
128
129         /* Reset DMAC device */
130         samd21_dmac.ctrl = 0;
131         samd21_dmac.ctrl = (1 << SAMD21_DMAC_CTRL_SWRST);
132         while (samd21_dmac.ctrl & (1 << SAMD21_DMAC_CTRL_SWRST))
133                 ;
134
135         samd21_dmac.baseaddr = (uint32_t) &samd21_dmac_desc[0];
136         samd21_dmac.wrbaddr = (uint32_t) &samd21_dmac_wrb[0];
137
138         samd21_dmac.swtrigctrl = 0;
139
140         /* Set QoS to highest value */
141         samd21_dmac.qosctrl = ((SAMD21_DMAC_QOSCTRL_HIGH << SAMD21_DMAC_QOSCTRL_DQOS) |
142                                (SAMD21_DMAC_QOSCTRL_HIGH << SAMD21_DMAC_QOSCTRL_FQOS) |
143                                (SAMD21_DMAC_QOSCTRL_HIGH << SAMD21_DMAC_QOSCTRL_WRBQOS));
144
145         /* Enable DMAC controller with all priority levels */
146         samd21_dmac.ctrl = ((1 << SAMD21_DMAC_CTRL_DMAENABLE) |
147                             (1 << SAMD21_DMAC_CTRL_LVLEN(0)) |
148                             (1 << SAMD21_DMAC_CTRL_LVLEN(1)) |
149                             (1 << SAMD21_DMAC_CTRL_LVLEN(2)) |
150                             (1 << SAMD21_DMAC_CTRL_LVLEN(3)));
151
152         /* Reset all DMAC channels */
153         for (ch = 0; ch < SAMD21_DMAC_NCHAN; ch++) {
154                 samd21_dmac.chid = ch;
155                 samd21_dmac.chctrla = (1 << SAMD21_DMAC_CHCTRLA_SWRST);
156                 while (samd21_dmac.chctrla & (1 << SAMD21_DMAC_CHCTRLA_SWRST))
157                         ;
158                 samd21_dmac.chintenset = (1 << SAMD21_DMAC_CHINTFLAG_TCMPL);
159         }
160
161         /* configure interrupts */
162         samd21_nvic_set_enable(SAMD21_NVIC_ISR_DMAC_POS);
163         samd21_nvic_set_priority(SAMD21_NVIC_ISR_DMAC_POS, 3);
164 }