summaryrefslogtreecommitdiff
path: root/firmware/target/arm/as3525/ascodec-as3525.c
blob: ca77be32a46ccd670e4d35365254afd8dbe91db3 (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
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2008 by Bertrik Sikken
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/*
    Provides access to the codec/charger/rtc/adc part of the as3525.
    This part is on address 0x46 of the internal i2c bus in the as3525.
    Registers in the codec part seem to be nearly identical to the registers
    in the AS3514 (used in the "v1" versions of the sansa c200 and e200).
    
    I2C register description:
    * I2C2_CNTRL needs to be set to 0x51 for transfers to work at all.
      bit 0: ? possibly related to using ACKs during transfers
      bit 1: direction of transfer (0 = write, 1 = read)
      bit 2: use 2-byte slave address
    * I2C2_IMR, I2C2_RIS, I2C2_MIS, I2C2_INT_CLR interrupt bits:
      bit 2: byte read interrupt
      bit 3: byte write interrupt
      bit 4: ? possibly some kind of error status
      bit 7: ACK error
    * I2C2_SR (status register) indicates in bit 0 if a transfer is busy.
    * I2C2_SLAD0 contains the i2c slave address to read from / write to.
    * I2C2_CPSR0/1 is the divider from the peripheral clock to the i2c clock.
    * I2C2_DACNT sets the number of bytes to transfer and actually starts it.
    
    When a transfer is attempted to a non-existing i2c slave address,
    interrupt bit 7 is raised and DACNT is not decremented after the transfer.
 */

#include "ascodec-target.h"
#include "clock-target.h"
#include "kernel.h"
#include "system.h"
#include "as3525.h"
#include "i2c.h"
#include "usb-target.h"

#define I2C2_DATA       *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x00))
#define I2C2_SLAD0      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x04))
#define I2C2_CNTRL      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x0C))
#define I2C2_DACNT      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x10))
#define I2C2_CPSR0      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x1C))
#define I2C2_CPSR1      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x20))
#define I2C2_IMR        *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x24))
#define I2C2_RIS        *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x28))
#define I2C2_MIS        *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x2C))
#define I2C2_SR         *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x30))
#define I2C2_INT_CLR    *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x40))
#define I2C2_SADDR      *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x44))

#define I2C2_CNTRL_MASTER     0x01
#define I2C2_CNTRL_READ       0x02
#define I2C2_CNTRL_WRITE      0x00
#define I2C2_CNTRL_RESET      0x10
#define I2C2_CNTRL_REPSTARTEN 0x40

#define I2C2_CNTRL_DEFAULT (I2C2_CNTRL_MASTER|I2C2_CNTRL_REPSTARTEN|I2C2_CNTRL_RESET)

#define I2C2_IRQ_TXEMPTY      0x04
#define I2C2_IRQ_RXFULL       0x08
#define I2C2_IRQ_RXOVER       0x10
#define I2C2_IRQ_ACKTIMEO     0x80

#define REQ_UNFINISHED 0
#define REQ_FINISHED   1
#define REQ_RETRY      2

#ifdef DEBUG
#define IFDEBUG(x) x
#else
#define IFDEBUG(x)
#endif

#define ASCODEC_REQ_READ   0
#define ASCODEC_REQ_WRITE  1

/*
 * How many bytes we using in struct ascodec_request for the data buffer.
 * 4 fits the alignment best right now.
 * We don't actually use more than 3 at the moment (when reading interrupts)
 * Upper limit would be 255 since DACNT is 8 bits!
 */
#define ASCODEC_REQ_MAXLEN 4

typedef void (ascodec_cb_fn)(unsigned const char *data, unsigned cnt);

struct ascodec_request {
    unsigned char type;
    unsigned char index;
    unsigned char status;
    unsigned char cnt;
    unsigned char data[ASCODEC_REQ_MAXLEN];
    struct wakeup wkup;
    ascodec_cb_fn *callback;
    struct ascodec_request *next;
};


static struct mutex as_mtx;

static int ascodec_enrd0_shadow = 0;

static unsigned char *req_data_ptr = NULL;
static struct ascodec_request *req_head = NULL;
static struct ascodec_request *req_tail = NULL;

static struct wakeup adc_wkup;
static struct ascodec_request as_audio_req;

#ifdef DEBUG
static int int_audio_ctr = 0;
static int int_chg_finished = 0;
static int int_chg_insert = 0;
static int int_chg_remove = 0;
static int int_usb_insert = 0;
static int int_usb_remove = 0;
static int int_rtc = 0;
static int int_adc = 0;
#endif /* DEBUG */

/* returns != 0 when busy */
static inline int i2c_busy(void)
{
    return (I2C2_SR & 1);
}

static void ascodec_finish_req(struct ascodec_request *req)
{
    /*
     * Wait if still busy, unfortunately this happens since
     * the controller is running at a low divisor, so it's
     * still busy when we serviced the interrupt.
     * I tried upping the i2c speed to 4MHz which
     * made the number of busywait cycles much smaller
     * (none for reads and only a few for writes),
     * but who knows if it's reliable at that frequency. ;)
     * For one thing, 8MHz doesn't work, so 4MHz is likely
     * borderline.
     * In general writes need much more wait cycles than reads
     * for some reason, possibly because we read the data register
     * for reads, which will likely block the processor while
     * the i2c controller responds to the register read.
     */
    while (i2c_busy());

    /* disable clock */
    CGU_PERI &= ~CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE;

    req->status = 1;

    if (req->callback) {
        req->callback(req->data, req_data_ptr - req->data);
    }
    wakeup_signal(&req->wkup);

    req_head = req->next;
    req->next = NULL;
    if (req_head == NULL)
        req_tail = NULL;

}

static int ascodec_continue_req(struct ascodec_request *req, int irq_status)
{
    if ((irq_status & (I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO)) > 0) {
        /* some error occured, restart the request */
        return REQ_RETRY;
    }
    if (req->type == ASCODEC_REQ_READ &&
        (irq_status & I2C2_IRQ_RXFULL) > 0) {
        *(req_data_ptr++) = I2C2_DATA;
    } else {
        if (req->cnt > 1 &&
            (irq_status & I2C2_IRQ_TXEMPTY) > 0) {
            I2C2_DATA = *(req_data_ptr++);
        }
    }

    req->index++;
    if (--req->cnt > 0)
        return REQ_UNFINISHED;

    return REQ_FINISHED;
}

static void ascodec_start_req(struct ascodec_request *req)
{
    int unmask = 0;

    /* enable clock */
    CGU_PERI |= CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE;

    /* start transfer */
    I2C2_SADDR = req->index;
    if (req->type == ASCODEC_REQ_READ) {
        req_data_ptr = req->data;
        /* start transfer */
        I2C2_CNTRL = I2C2_CNTRL_DEFAULT | I2C2_CNTRL_READ;
        unmask = I2C2_IRQ_RXFULL|I2C2_IRQ_RXOVER;
    } else {
        req_data_ptr = &req->data[1];
        I2C2_CNTRL = I2C2_CNTRL_DEFAULT | I2C2_CNTRL_WRITE;
        I2C2_DATA = req->data[0];
        unmask = I2C2_IRQ_TXEMPTY|I2C2_IRQ_ACKTIMEO;
    }

    I2C2_DACNT = req->cnt;
    I2C2_IMR |= unmask; /* enable interrupts */
}

void INT_I2C_AUDIO(void)
{
    int irq_status = I2C2_MIS;
    int status = REQ_FINISHED;

    if (req_head != NULL)
        status = ascodec_continue_req(req_head, irq_status);

    I2C2_INT_CLR |= irq_status; /* clear interrupt status */

    if (status != REQ_UNFINISHED) {
        /* mask rx/tx interrupts */
        I2C2_IMR &= ~(I2C2_IRQ_TXEMPTY|I2C2_IRQ_RXFULL|
                      I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO);

        if (status == REQ_FINISHED)
            ascodec_finish_req(req_head);

        /*
         * If status == REQ_RETRY, this will restart
         * the request because we didn't remove it from
         * the request list
         */
        if (req_head)
            ascodec_start_req(req_head);
    }
}

void i2c_init(void)
{
}

/* initialises the internal i2c bus and prepares for transfers to the codec */
void ascodec_init(void)
{
    int prescaler;

    mutex_init(&as_mtx);
    wakeup_init(&adc_wkup);

    /* enable clock */
    CGU_PERI |= CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE;

    /* prescaler for i2c clock */
    prescaler = AS3525_I2C_PRESCALER;
    I2C2_CPSR0 = prescaler & 0xFF;          /* 8 lsb */
    I2C2_CPSR1 = (prescaler >> 8) & 0x3;    /* 2 msb */
    
    /* set i2c slave address of codec part */
    I2C2_SLAD0 = AS3514_I2C_ADDR << 1;

    I2C2_CNTRL = I2C2_CNTRL_DEFAULT;

    I2C2_IMR = 0x00;          /* disable interrupts */
    I2C2_INT_CLR |= I2C2_RIS; /* clear interrupt status */
    VIC_INT_ENABLE = INTERRUPT_I2C_AUDIO;
    VIC_INT_ENABLE = INTERRUPT_AUDIO;

    /* detect if USB was connected at startup since there is no transition */
    ascodec_enrd0_shadow = ascodec_read(AS3514_IRQ_ENRD0);
    if(ascodec_enrd0_shadow & USB_STATUS)
        usb_insert_int();
    else
        usb_remove_int();

    /* Generate irq for usb+charge status change */
    ascodec_write(AS3514_IRQ_ENRD0,
#ifdef CONFIG_CHARGING /* m200v4 can't charge */
        IRQ_CHGSTAT | IRQ_ENDOFCH |
#endif
        IRQ_USBSTAT);

#if CONFIG_CPU == AS3525v2
    /* XIRQ = IRQ, active low reset signal, 6mA push-pull output */
    ascodec_write_pmu(0x1a, 3, (1<<2)|3); /* 1A-3 = Out_Cntr3 register */
    /* Generate irq on (rtc,) adc change */
    ascodec_write(AS3514_IRQ_ENRD2, /*IRQ_RTC |*/ IRQ_ADC);
#else
    /* Generate irq for push-pull, active high, irq on rtc+adc change */
    ascodec_write(AS3514_IRQ_ENRD2, IRQ_PUSHPULL | IRQ_HIGHACTIVE |
                                    /*IRQ_RTC |*/ IRQ_ADC);
#endif
}

static void ascodec_req_init(struct ascodec_request *req, int type,
                      unsigned int index, unsigned int cnt)
{
    wakeup_init(&req->wkup);
    req->next = NULL;
    req->callback = NULL;
    req->type = type;
    req->index = index;
    req->cnt = cnt;
}

static void ascodec_submit(struct ascodec_request *req)
{
    int oldlevel = disable_irq_save();

    req->status = 0;

    if (req_head == NULL) {
        req_tail = req_head = req;
        ascodec_start_req(req);
    } else {
        req_tail->next = req;
        req_tail = req;
    }

    restore_irq(oldlevel);
}

static int irq_disabled(void)
{
    unsigned long cpsr;

    asm volatile ("mrs %0, cpsr" : "=r"(cpsr));

    return (cpsr & IRQ_STATUS) == IRQ_DISABLED;
}

static void ascodec_wait(struct ascodec_request *req)
{
    if (!irq_disabled()) {
        wakeup_wait(&req->wkup, TIMEOUT_BLOCK);
        return;
    }

    while (req->status == 0) {
        if (I2C2_MIS) INT_I2C_AUDIO();
    }
}

/*
 * The request struct passed in must be allocated statically.
 * If you call ascodec_async_write from different places, each
 * call needs it's own request struct.
 */
static void ascodec_async_write(unsigned int index, unsigned int value,
                         struct ascodec_request *req)
{
    if (index == AS3514_CVDD_DCDC3) /* prevent setting of the LREG_CP_not bit */
        value &= ~(1 << 5);

    ascodec_req_init(req, ASCODEC_REQ_WRITE, index, 1);
    req->data[0] = value;
    ascodec_submit(req);
}

/* returns 0 on success, <0 otherwise */
int ascodec_write(unsigned int index, unsigned int value)
{
    struct ascodec_request req;

    ascodec_async_write(index, value, &req);
    ascodec_wait(&req);

    return 0;
}

/*
 * The request struct passed in must be allocated statically.
 * If you call ascodec_async_read from different places, each
 * call needs it's own request struct.
 * If len is bigger than ASCODEC_REQ_MAXLEN it will be
 * set to ASCODEC_REQ_MAXLEN.
 */
static void ascodec_async_read(unsigned int index, unsigned int len,
                        struct ascodec_request *req, ascodec_cb_fn *cb)
{
    if (len > ASCODEC_REQ_MAXLEN)
        len = ASCODEC_REQ_MAXLEN; /* can't fit more in one request */

    ascodec_req_init(req, ASCODEC_REQ_READ, index, len);
    req->callback = cb;
    ascodec_submit(req);
}

/* returns value read on success, <0 otherwise */
int ascodec_read(unsigned int index)
{
    struct ascodec_request req;

    ascodec_async_read(index, 1, &req, NULL);
    ascodec_wait(&req);

    return req.data[0];
}

int ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data)
{
    int i, j;
    struct ascodec_request req;

    /* index and cnt will be filled in later, just use 0 */
    ascodec_req_init(&req, ASCODEC_REQ_READ, 0, 0);

    i = 0;
    while (len > 0) {
        int cnt = len > ASCODEC_REQ_MAXLEN ? ASCODEC_REQ_MAXLEN : len;

        req.index = index;
        req.cnt = cnt;

        ascodec_submit(&req);
        ascodec_wait(&req);

        for (j=0; j<cnt; j++) data[i++] = req.data[j];

        len   -= cnt;
        index += cnt;
    }

    return i;
}

static void ascodec_read_cb(unsigned const char *data, unsigned int len)
{
    if (UNLIKELY(len != 3)) /* some error happened? */
        panicf("INT_AUDIO callback got %d regs", len);

    if (data[0] & CHG_ENDOFCH) { /* chg finished */
        ascodec_enrd0_shadow |= CHG_ENDOFCH;
        IFDEBUG(int_chg_finished++);
    }
    if (data[0] & CHG_CHANGED) { /* chg status changed */
        if (data[0] & CHG_STATUS) {
            ascodec_enrd0_shadow |= CHG_STATUS;
            IFDEBUG(int_chg_insert++);
        } else {
            ascodec_enrd0_shadow &= ~CHG_STATUS;
            IFDEBUG(int_chg_remove++);
        }
    }
    if (data[0] & USB_CHANGED) { /* usb status changed */
        if (data[0] & USB_STATUS) {
            IFDEBUG(int_usb_insert++);
            usb_insert_int();
        } else {
            IFDEBUG(int_usb_remove++);
            usb_remove_int();
        }
    }
    if (data[2] & IRQ_RTC) { /* rtc irq */
        /*
         * Can be configured for once per second or once per minute,
         * default is once per second
         */
        IFDEBUG(int_rtc++);
    }
    if (data[2] & IRQ_ADC) { /* adc finished */
        IFDEBUG(int_adc++);
        wakeup_signal(&adc_wkup);
    }
    VIC_INT_ENABLE = INTERRUPT_AUDIO;
}

void INT_AUDIO(void)
{
    VIC_INT_EN_CLEAR = INTERRUPT_AUDIO;
    IFDEBUG(int_audio_ctr++);

    ascodec_async_read(AS3514_IRQ_ENRD0, 3, &as_audio_req, ascodec_read_cb);
}

void ascodec_wait_adc_finished(void)
{
    wakeup_wait(&adc_wkup, TIMEOUT_BLOCK);
}

#ifdef CONFIG_CHARGING
bool ascodec_endofch(void)
{
    bool ret = ascodec_enrd0_shadow & CHG_ENDOFCH;
    ascodec_enrd0_shadow &= ~CHG_ENDOFCH; // clear interrupt
    return ret;
}

bool ascodec_chg_status(void)
{
    return ascodec_enrd0_shadow & CHG_STATUS;
}
#endif /* CONFIG_CHARGING */

/*
 * NOTE:
 * After the conversion to interrupts, ascodec_(lock|unlock) are only used by
 * adc-as3514.c to protect against other threads corrupting the result by using
 * the ADC at the same time.
 * Concurrent ascodec_(async_)?(read|write) calls are instead protected
 * because ascodec_submit() is atomic and concurrent requests will wait
 * in the queue until the current request is finished.
 */
void ascodec_lock(void)
{
    mutex_lock(&as_mtx);
}

void ascodec_unlock(void)
{
    mutex_unlock(&as_mtx);
}