summaryrefslogtreecommitdiffstats
path: root/src/target/firmware/abb/twl3025.c
blob: 3e7fabb48fbb612ab42e467f55756b5fc0ffa946 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/* Driver for Analog Baseband Circuit (TWL3025) */

/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
 *
 * All Rights Reserved
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include <stdint.h>
#include <stdio.h>

#include <debug.h>
#include <delay.h>
#include <memory.h>
#include <spi.h>
#include <calypso/irq.h>
#include <calypso/tsp.h>
#include <calypso/tpu.h>
#include <abb/twl3025.h>
#include <asm/system.h>

/* TWL3025 */
#define REG_PAGE(n)	(n >> 7)
#define REG_ADDR(n)	(n & 0x3f)

#define TWL3025_DEV_IDX		0	/* On the SPI bus */
#define TWL3025_TSP_DEV_IDX	0	/* On the TSP bus */

/* values encountered on a GTA-02 for GSM900 (the same for GSM1800!?) */
const uint16_t twl3025_default_ramp[16] = {
	ABB_RAMP_VAL( 0,  0),
	ABB_RAMP_VAL( 0, 11),
	ABB_RAMP_VAL( 0, 31),
	ABB_RAMP_VAL( 0, 31),
	ABB_RAMP_VAL( 0, 31),
	ABB_RAMP_VAL( 0, 24),
	ABB_RAMP_VAL( 0,  0),
	ABB_RAMP_VAL( 0,  0),
	ABB_RAMP_VAL( 9,  0),
	ABB_RAMP_VAL(18,  0),
	ABB_RAMP_VAL(25,  0),
	ABB_RAMP_VAL(31,  0),
	ABB_RAMP_VAL(30,  0),
	ABB_RAMP_VAL(15,  0),
	ABB_RAMP_VAL( 0,  0),
	ABB_RAMP_VAL( 0,  0),
};

typedef enum pwon_state {
	PWON_IDLE,
	PWON_DETECTED,
	PWON_REPORTED,
} pwon_state_t;

struct twl3025 {
	uint8_t page;
	pwon_state_t pwon;
};
static struct twl3025 twl3025_state;

/* Switch the register page of the TWL3025 */
static void twl3025_switch_page(uint8_t page)
{
	if (page == 0)
		twl3025_reg_write(PAGEREG, 1 << 0);
	else
		twl3025_reg_write(PAGEREG, 1 << 1);

	twl3025_state.page = page;
}

static void handle_charger(void)
{
	uint16_t status;
	printd("handle_charger();");

	status = twl3025_reg_read(VRPCSTS);
//	printd("\nvrpcsts: 0x%02x", status);

	if (status & 0x40) {
		printd(" inserted\n");
	} else {
		printd(" removed\n");
	}

//	twl3025_dump_madc();
}

static void handle_adc_done(void)
{
	printd("handle_adc_done();");
}

static void twl3025_irq(enum irq_nr nr)
{
	uint16_t src;
	printd("twl3025_irq: 0x%02x\n",nr);
	switch (nr){
	case IRQ_EXTERNAL: // charger in/out, pwrbtn, adc done
		src = twl3025_reg_read(ITSTATREG);
//		printd("itstatreg 0x%02x\n", src);
		if ((src & 0x04) && twl3025_state.pwon == PWON_IDLE)
			twl3025_state.pwon = PWON_DETECTED;
		if (src & 0x08)
			handle_charger();
		if (src & 0x20)
			handle_adc_done();
		break;
	case IRQ_EXTERNAL_FIQ: // vcc <2.8V emergency power off
		puts("\nBROWNOUT!1!");
		twl3025_power_off();
		break;
	default:
		return;
	}
}

void twl3025_init(void)
{
	spi_init();
	twl3025_switch_page(0);
	twl3025_clk13m(1);
	twl3025_reg_write(AFCCTLADD, 0x01);	/* AFCCK(1:0) must not be zero! */
	twl3025_unit_enable(TWL3025_UNIT_AFC, 1);
	twl3025_state.pwon = PWON_IDLE;

	irq_register_handler(IRQ_EXTERNAL, &twl3025_irq);
	irq_config(IRQ_EXTERNAL, 0, 0, 0);
	irq_enable(IRQ_EXTERNAL);

	irq_register_handler(IRQ_EXTERNAL_FIQ, &twl3025_irq);
	irq_config(IRQ_EXTERNAL_FIQ, 1, 0, 0);
	irq_enable(IRQ_EXTERNAL_FIQ);
}

void twl3025_reg_write(uint8_t reg, uint16_t data)
{
	uint16_t tx;

	printd("tw3025_reg_write(%u,%u)=0x%04x\n", REG_PAGE(reg),
		REG_ADDR(reg), data);

	if (reg != PAGEREG && REG_PAGE(reg) != twl3025_state.page)
		twl3025_switch_page(REG_PAGE(reg));

	tx = ((data & 0x3ff) << 6) | (REG_ADDR(reg) << 1);

	spi_xfer(TWL3025_DEV_IDX, 16, &tx, NULL);
}

void twl3025_tsp_write(uint8_t data)
{
	tsp_write(TWL3025_TSP_DEV_IDX, 7, data);
}

uint16_t twl3025_reg_read(uint8_t reg)
{
	uint16_t tx, rx;

	if (REG_PAGE(reg) != twl3025_state.page)
		twl3025_switch_page(REG_PAGE(reg));

	tx = (REG_ADDR(reg) << 1) | 1;

	/* A read cycle contains two SPI transfers */
	spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx);
	delay_us(400);
	spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx);

	rx >>= 6;

	printd("tw3025_reg_read(%u,%u)=0x%04x\n", REG_PAGE(reg),
		REG_ADDR(reg), rx);

	return rx;
}

static void twl3025_wait_ibic_access(void)
{
	/* Wait 6 * 32kHz clock cycles for first IBIC access (187us + 10% = 210us) */
	delay_us(400);
}

int twl3025_get_pwon(void)
{
	switch (twl3025_state.pwon) {
	case PWON_DETECTED:
		twl3025_state.pwon = PWON_REPORTED;
		break;
	case PWON_REPORTED:
		if (twl3025_reg_read(VRPCSTS) & 0x10)
			twl3025_state.pwon = PWON_IDLE;
		break;
	case PWON_IDLE:
	default:
		break;
	}

	return (twl3025_state.pwon != PWON_IDLE);
}

void twl3025_power_off(void)
{
	unsigned long flags;

	/* turn off all IRQs, since received frames cannot be
	 * handled form here. (otherwise the message allocation
	 * runs out of memory) */
	local_firq_save(flags);

	/* poll PWON status and power off the phone when the
	 * powerbutton has been released (otherwise it will
	 * poweron immediately again) */
	while (!(twl3025_reg_read(VRPCSTS) & 0x10)) { };
	twl3025_reg_write(VRPCDEV, 0x01);
}

void twl3025_power_off_now(void)
{
	/* The phone will restart if the power butten has not been released.
	 * This can be useful for development. */
	unsigned long flags;
	local_firq_save(flags);
	twl3025_reg_write(VRPCDEV, 0x01);
}

void twl3025_clk13m(int enable)
{
	if (enable) {
		twl3025_reg_write(TOGBR2, TOGBR2_ACTS);
		twl3025_wait_ibic_access();
		/* for whatever reason we need to do this twice */
		twl3025_reg_write(TOGBR2, TOGBR2_ACTS);
		twl3025_wait_ibic_access();
	} else {
		twl3025_reg_write(TOGBR2, TOGBR2_ACTR);
		twl3025_wait_ibic_access();
	}
}

#define	TSP_DELAY	6	/* 13* Tclk6M5 = ~ 3 GSM Qbits + 3 TPU instructions */
#define BDLON_TO_BDLCAL	6
#define BDLCAL_DURATION	66
#define BDLON_TO_BDLENA	7
#define BULON_TO_BULENA	16
#define BULON_TO_BULCAL	17
#define BULCAL_DURATION	143	/* really that long? */

/* bdl_ena - TSP_DELAY - BDLCAL_DURATION - TSP_DELAY - BDLON_TO_BDLCAL - TSP_DELAY */
#define DOWNLINK_DELAY	(3 * TSP_DELAY + BDLCAL_DURATION + BDLON_TO_BDLCAL)

/* Enqueue a series of TSP commands in the TPU to (de)activate the downlink path */
void twl3025_downlink(int on, int16_t at)
{
	int16_t bdl_ena = at - TSP_DELAY - 6;

	if (on) {
		if (bdl_ena < 0)
			printf("BDLENA time negative (%d)\n", bdl_ena);
		/* calibration should be done just before BDLENA */
		tpu_enq_at(bdl_ena - DOWNLINK_DELAY);
		/* bdl_ena - TSP_DELAY - BDLCAL_DURATION - TSP_DELAY - BDLON_TO_BDLCAL - TSP_DELAY */
		twl3025_tsp_write(BDLON);
		/* bdl_ena - TSP_DELAY - BDLCAL_DURATION - TSP_DELAY - BDLON_TO_BDLCAL */
		tpu_enq_wait(BDLON_TO_BDLCAL - TSP_DELAY);
		/* bdl_ena - TSP_DELAY - BDLCAL_DURATION - TSP_DELAY */
		twl3025_tsp_write(BDLON | BDLCAL);
		/* bdl_ena - TSP_DELAY - BDLCAL_DURATION */
		tpu_enq_wait(BDLCAL_DURATION - TSP_DELAY);
		/* bdl_ena - TSP_DELAY */
		twl3025_tsp_write(BDLON);
		//tpu_enq_wait(BDLCAL_TO_BDLENA)	this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY
		tpu_enq_at(bdl_ena);
		twl3025_tsp_write(BDLON | BDLENA);
	} else {
		tpu_enq_at(bdl_ena);
		twl3025_tsp_write(BDLON);
		//tpu_enq_wait(nBDLENA_TO_nBDLON)	this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY
		twl3025_tsp_write(0);
	}
}

/* bdl_ena - 35 - TSP_DELAY - BULCAL_DURATION - TSP_DELAY - BULON_TO_BULCAL - TSP_DELAY */
#define UPLINK_DELAY (3 * TSP_DELAY + BULCAL_DURATION + BULON_TO_BULCAL + 35)

void twl3025_uplink(int on, int16_t at)
{
	int16_t bul_ena = at - TSP_DELAY - 6;

	if (bul_ena < 0)
		printf("BULENA time negative (%d)\n", bul_ena);
	if (on) {
		/* calibration should  be done just before BULENA */
		tpu_enq_at(bul_ena - UPLINK_DELAY);
		/* bdl_ena - 35 - TSP_DELAY - BULCAL_DURATION - TSP_DELAY - BULON_TO_BULCAL - TSP_DELAY */
		twl3025_tsp_write(BULON);
		/* bdl_ena - 35 - TSP_DELAY - BULCAL_DURATION - TSP_DELAY - BULON_TO_BULCAL */
		tpu_enq_wait(BULON_TO_BULCAL - TSP_DELAY);
		/* bdl_ena - 35 - TSP_DELAY - BULCAL_DURATION - TSP_DELAY */
		twl3025_tsp_write(BULON | BULCAL);
		/* bdl_ena - 35 - TSP_DELAY - BULCAL_DURATION */
		tpu_enq_wait(BULCAL_DURATION - TSP_DELAY);
		/* bdl_ena - 35 - TSP_DELAY */
		twl3025_tsp_write(BULON);
		/* bdl_ena - 35 */
		tpu_enq_wait(35);	/* minimum time required to bring the ramp up (really needed?) */
		tpu_enq_at(bul_ena);
		twl3025_tsp_write(BULON | BULENA);
	} else {
		tpu_enq_at(bul_ena);
		twl3025_tsp_write(BULON);
		tpu_enq_wait(35);	/* minimum time required to bring the ramp down (needed!) */
		twl3025_tsp_write(0);
	}
}

void twl3025_afc_set(int16_t val)
{
	printf("twl3025_afc_set(%d)\n", val);

	if (val > 4095)
		val = 4095;
	else if (val <= -4096)
		val = -4096;

	/* FIXME: we currently write from the USP rather than BSP */
	twl3025_reg_write(AUXAFC2, val >> 10);
	twl3025_reg_write(AUXAFC1, val & 0x3ff);
}

int16_t twl3025_afc_get(void)
{
	int16_t val;

	val = (twl3025_reg_read(AUXAFC2) & 0x7);
	val = val << 10;
	val = val | (twl3025_reg_read(AUXAFC1) & 0x3ff);

	if (val > 4095)
		val = -(8192 - val);
	return val;
}

void twl3025_unit_enable(enum twl3025_unit unit, int on)
{
	uint16_t togbr1 = 0;

	switch (unit) {
	case TWL3025_UNIT_AFC:
		if (on)
			togbr1 = (1 << 7);
		else
			togbr1 = (1 << 6);
		break;
	case TWL3025_UNIT_MAD:
		if (on)
			togbr1 = (1 << 9);
		else
			togbr1 = (1 << 8);
		break;
	case TWL3025_UNIT_ADA:
		if (on)
			togbr1 = (1 << 5);
		else
			togbr1 = (1 << 4);
	case TWL3025_UNIT_VDL:
		if (on)
			togbr1 = (1 << 3);
		else
			togbr1 = (1 << 2);
		break;
	case TWL3025_UNIT_VUL:
		if (on)
			togbr1 = (1 << 1);
		else
			togbr1 = (1 << 0);
		break;
	}
	twl3025_reg_write(TOGBR1, togbr1);
}

uint8_t twl3025_afcout_get(void)
{
	return twl3025_reg_read(AFCOUT) & 0xff;
}

void twl3025_afcout_set(uint8_t val)
{
	twl3025_reg_write(AFCCTLADD, 0x05);
	twl3025_reg_write(AFCOUT, val);
}