Initial import of OsmocomBB into git repository
[osmocom-bb.git] / src / target / firmware / abb / twl3025.c
1 /* Driver for Analog Baseband Circuit (TWL3025) */
2
3 /* (C) 2010 by Harald Welte <laforge@gnumonks.org>
4  *
5  * All Rights Reserved
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  */
22
23 #include <stdint.h>
24 #include <stdio.h>
25
26 #include <debug.h>
27 #include <delay.h>
28 #include <memory.h>
29 #include <spi.h>
30 #include <calypso/irq.h>
31 #include <calypso/tsp.h>
32 #include <calypso/tpu.h>
33 #include <abb/twl3025.h>
34
35 /* TWL3025 */
36 #define REG_PAGE(n)     (n >> 7)
37 #define REG_ADDR(n)     (n & 0x3f)
38
39 #define TWL3025_DEV_IDX         0       /* On the SPI bus */
40 #define TWL3025_TSP_DEV_IDX     0       /* On the TSP bus */
41
42 struct twl3025 {
43         uint8_t page;
44 };
45 static struct twl3025 twl3025_state;
46
47 /* Switch the register page of the TWL3025 */
48 static void twl3025_switch_page(uint8_t page)
49 {
50         if (page == 0)
51                 twl3025_reg_write(PAGEREG, 1 << 0);
52         else
53                 twl3025_reg_write(PAGEREG, 1 << 1);
54
55         twl3025_state.page = page;
56 }
57
58 static void handle_charger(void)
59 {
60         uint16_t status;
61         printd("handle_charger();");
62
63         status = twl3025_reg_read(VRPCSTS);
64 //      printd("\nvrpcsts: 0x%02x", status);
65
66         if (status & 0x40) {
67                 printd(" inserted\n");
68         } else {
69                 printd(" removed\n");
70         }
71
72 //      twl3025_dump_madc();
73 }
74
75 static void handle_adc_done(void)
76 {
77         printd("handle_adc_done();");
78 }
79
80 static void twl3025_irq(enum irq_nr nr)
81 {
82         uint16_t src;
83         printd("twl3025_irq: 0x%02x\n",nr);
84         switch (nr){
85         case IRQ_EXTERNAL: // charger in/out, pwrbtn, adc done
86                 src = twl3025_reg_read(ITSTATREG);
87 //              printd("itstatreg 0x%02x\n", src);
88                 if (src & 0x08)
89                         handle_charger();
90                 if (src & 0x20)
91                         handle_adc_done();
92                 break;
93         case IRQ_EXTERNAL_FIQ: // vcc <2.8V emergency power off
94                 puts("\nBROWNOUT!1!");
95                 twl3025_power_off();
96                 break;
97         default:
98                 return;
99         }
100 }
101
102 void twl3025_init(void)
103 {
104         spi_init();
105         twl3025_switch_page(0);
106         twl3025_clk13m(1);
107         twl3025_reg_write(AFCCTLADD, 0x01);     /* AFCCK(1:0) must not be zero! */
108         twl3025_unit_enable(TWL3025_UNIT_AFC, 1);
109
110         irq_register_handler(IRQ_EXTERNAL, &twl3025_irq);
111         irq_config(IRQ_EXTERNAL, 0, 0, 0);
112         irq_enable(IRQ_EXTERNAL);
113
114         irq_register_handler(IRQ_EXTERNAL_FIQ, &twl3025_irq);
115         irq_config(IRQ_EXTERNAL_FIQ, 1, 0, 0);
116         irq_enable(IRQ_EXTERNAL_FIQ);
117 }
118
119 void twl3025_reg_write(uint8_t reg, uint16_t data)
120 {
121         uint16_t tx;
122
123         printd("tw3025_reg_write(%u,%u)=0x%04x\n", REG_PAGE(reg),
124                 REG_ADDR(reg), data);
125
126         if (reg != PAGEREG && REG_PAGE(reg) != twl3025_state.page)
127                 twl3025_switch_page(REG_PAGE(reg));
128
129         tx = ((data & 0x3ff) << 6) | (REG_ADDR(reg) << 1);
130
131         spi_xfer(TWL3025_DEV_IDX, 16, &tx, NULL);
132 }
133
134 void twl3025_tsp_write(uint8_t data)
135 {
136         tsp_write(TWL3025_TSP_DEV_IDX, 7, data);
137 }
138
139 uint16_t twl3025_reg_read(uint8_t reg)
140 {
141         uint16_t tx, rx;
142
143         if (REG_PAGE(reg) != twl3025_state.page)
144                 twl3025_switch_page(REG_PAGE(reg));
145
146         tx = (REG_ADDR(reg) << 1) | 1;
147
148         /* A read cycle contains two SPI transfers */
149         spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx);
150         delay_ms(1);
151         spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx);
152
153         rx >>= 6;
154
155         printd("tw3025_reg_read(%u,%u)=0x%04x\n", REG_PAGE(reg),
156                 REG_ADDR(reg), rx);
157
158         return rx;
159 }
160
161 static void twl3025_wait_ibic_access(void)
162 {
163         /* Wait 6 * 32kHz clock cycles for first IBIC access (187us + 10% = 210us) */
164         delay_ms(1);
165 }
166
167 void twl3025_power_off(void)
168 {
169         twl3025_reg_write(VRPCDEV, 0x01);
170 }
171
172 void twl3025_clk13m(int enable)
173 {
174         if (enable) {
175                 twl3025_reg_write(TOGBR2, TOGBR2_ACTS);
176                 twl3025_wait_ibic_access();
177                 /* for whatever reason we need to do this twice */
178                 twl3025_reg_write(TOGBR2, TOGBR2_ACTS);
179                 twl3025_wait_ibic_access();
180         } else {
181                 twl3025_reg_write(TOGBR2, TOGBR2_ACTR);
182                 twl3025_wait_ibic_access();
183         }
184 }
185
186 #define TSP_DELAY       6       /* 13* Tclk6M5 = ~ 3 GSM Qbits + 3 TPU instructions */
187 #define BDLON_TO_BDLCAL 6
188 #define BDLCAL_DURATION 66
189 #define BDLON_TO_BDLENA 7
190 #define BULON_TO_BULENA 16
191
192 /* Enqueue a series of TSP commands in the TPU to (de)activate the downlink path */
193 void twl3025_downlink(int on, int16_t at)
194 {
195         int16_t bdl_ena = at - TSP_DELAY - 6;
196
197         if (on) {
198                 if (bdl_ena < 0)
199                         printf("BDLENA time negative (%d)\n", bdl_ena);
200                 /* FIXME: calibration should be done just before BDLENA */
201                 twl3025_tsp_write(BDLON);
202                 tpu_enq_wait(BDLON_TO_BDLCAL - TSP_DELAY);
203                 twl3025_tsp_write(BDLON | BDLCAL);
204                 tpu_enq_wait(BDLCAL_DURATION - TSP_DELAY);
205                 twl3025_tsp_write(BDLON);
206                 //tpu_enq_wait(BDLCAL_TO_BDLENA)        this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY
207                 tpu_enq_at(bdl_ena);
208                 twl3025_tsp_write(BDLON | BDLENA);
209         } else {
210                 tpu_enq_at(bdl_ena);
211                 twl3025_tsp_write(BDLON);
212                 //tpu_enq_wait(nBDLENA_TO_nBDLON)       this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY
213                 twl3025_tsp_write(0);
214         }
215 }
216
217 void twl3025_afc_set(int16_t val)
218 {
219         printf("twl3025_afc_set(%d)\n", val);
220
221         if (val > 4095)
222                 val = 4095;
223         else if (val <= -4096)
224                 val = -4096;
225
226         /* FIXME: we currently write from the USP rather than BSP */
227         twl3025_reg_write(AUXAFC2, val >> 10);
228         twl3025_reg_write(AUXAFC1, val & 0x3ff);
229 }
230
231 int16_t twl3025_afc_get(void)
232 {
233         int16_t val;
234
235         val = (twl3025_reg_read(AUXAFC2) & 0x7);
236         val = val << 10;
237         val = val | (twl3025_reg_read(AUXAFC1) & 0x3ff);
238
239         if (val > 4095)
240                 val = -(8192 - val);
241         return val;
242 }
243
244 void twl3025_unit_enable(enum twl3025_unit unit, int on)
245 {
246         uint16_t togbr1 = 0;
247
248         switch (unit) {
249         case TWL3025_UNIT_AFC:
250                 if (on)
251                         togbr1 = (1 << 7);
252                 else
253                         togbr1 = (1 << 6);
254                 break;
255         case TWL3025_UNIT_MAD:
256                 if (on)
257                         togbr1 = (1 << 9);
258                 else
259                         togbr1 = (1 << 8);
260                 break;
261         case TWL3025_UNIT_ADA:
262                 if (on)
263                         togbr1 = (1 << 5);
264                 else
265                         togbr1 = (1 << 4);
266         case TWL3025_UNIT_VDL:
267                 if (on)
268                         togbr1 = (1 << 3);
269                 else
270                         togbr1 = (1 << 2);
271                 break;
272         case TWL3025_UNIT_VUL:
273                 if (on)
274                         togbr1 = (1 << 1);
275                 else
276                         togbr1 = (1 << 0);
277                 break;
278         }
279         twl3025_reg_write(TOGBR1, togbr1);
280 }
281
282 uint8_t twl3025_afcout_get(void)
283 {
284         return twl3025_reg_read(AFCOUT) & 0xff;
285 }
286
287 void twl3025_afcout_set(uint8_t val)
288 {
289         twl3025_reg_write(AFCCTLADD, 0x05);
290         twl3025_reg_write(AFCOUT, val);
291 }