summaryrefslogtreecommitdiffstats
path: root/firmware/target/arm/imx233/audioout-imx233.c
blob: b27f7b37e2002e01ea1a1c898b182246786fc752 (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
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2011 by Amaury Pouly
 *
 * 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.
 *
 ****************************************************************************/
#include "audioout-imx233.h"
#include "clkctrl-imx233.h"
#include "rtc-imx233.h"
#include "pcm_sampr.h"
#include "string.h"

static int hp_vol_l, hp_vol_r;
static bool input_line1;
static struct timeout hp_unmute_oneshort;

static int hp_unmute_cb(struct timeout *tmo)
{
    (void) tmo;
    /* unmute HP */
    BF_CLR(AUDIOOUT_HPVOL, MUTE);
    return 0;
}

void imx233_audioout_preinit(void)
{

    /* Enable AUDIOOUT block */
    imx233_reset_block(&HW_AUDIOOUT_CTRL);
    /* Enable digital filter clock */
    imx233_clkctrl_enable(CLK_FILT, true);
    /* Enable DAC */
    BF_CLR(AUDIOOUT_ANACLKCTRL, CLKGATE);
    /* Set capless mode */
    BF_CLR(AUDIOOUT_PWRDN, CAPLESS);
    /* Set word-length to 16-bit */
    BF_SET(AUDIOOUT_CTRL, WORD_LENGTH);
    /* Power up DAC */
    BF_CLR(AUDIOOUT_PWRDN, DAC);
    /* Hold HP to ground to avoid pop, then release and power up HP */
    BF_SET(AUDIOOUT_ANACTRL, HP_HOLD_GND);
    BF_CLR(AUDIOOUT_PWRDN, HEADPHONE);
    /* Set HP mode to AB */
    BF_SET(AUDIOOUT_ANACTRL, HP_CLASSAB);
    /* change bias to -50% */
    BF_WR(AUDIOOUT_TEST, HP_I1_ADJ, 1);
    BF_WR(AUDIOOUT_REFCTRL, BIAS_CTRL, 1);
#if IMX233_SUBTARGET >= 3700
    BF_SET(AUDIOOUT_REFCTRL, RAISE_REF);
#endif
    BF_SET(AUDIOOUT_REFCTRL, XTAL_BGR_BIAS);
    /* Stop holding to ground */
    BF_CLR(AUDIOOUT_ANACTRL, HP_HOLD_GND);
    /* Set dmawait count to 31 (see errata, workaround random stop) */
    BF_WR(AUDIOOUT_CTRL, DMAWAIT_COUNT, 31);
    /* start converting audio */
    BF_SET(AUDIOOUT_CTRL, RUN);
    /* unmute DAC */
    HW_AUDIOOUT_DACVOLUME_CLR = BM_OR2(AUDIOOUT_DACVOLUME, MUTE_LEFT, MUTE_RIGHT);
    /* send a few samples to avoid pop */
    HW_AUDIOOUT_DATA = 0;
    HW_AUDIOOUT_DATA = 0;
    HW_AUDIOOUT_DATA = 0;
    HW_AUDIOOUT_DATA = 0;
}

void imx233_audioout_postinit(void)
{
    /* wait for everything to stabilize before unmuting */
    timeout_register(&hp_unmute_oneshort, hp_unmute_cb, HZ / 2, 0);
}

void imx233_audioout_close(void)
{
    /* Switch to class A */
    BF_CLR(AUDIOOUT_ANACTRL, HP_CLASSAB);
    /* Hold HP to ground */
    BF_SET(AUDIOOUT_ANACTRL, HP_HOLD_GND);
    /* Mute HP and power down */
    BF_SET(AUDIOOUT_HPVOL, MUTE);
    /* Power down HP */
    BF_SET(AUDIOOUT_PWRDN, HEADPHONE);
    /* Mute DAC */
    HW_AUDIOOUT_DACVOLUME_SET = BM_OR2(AUDIOOUT_DACVOLUME, MUTE_LEFT, MUTE_RIGHT);
    /* Power down DAC */
    BF_SET(AUDIOOUT_PWRDN, DAC);
    /* Gate off DAC */
    BF_SET(AUDIOOUT_ANACLKCTRL, CLKGATE);
    /* Disable digital filter clock */
    imx233_clkctrl_enable(CLK_FILT, false);
    /* will also gate off the module */
    BF_CLR(AUDIOOUT_CTRL, RUN);
}

/* volume in half dB
 * don't check input values */
static void set_dac_vol(int vol_l, int vol_r)
{
    /* minimum is -100dB and max is 0dB */
    vol_l = MAX(-200, MIN(vol_l, 0));
    vol_r = MAX(-200, MIN(vol_r, 0));
    /* unmute, enable zero cross and set volume.
     * 0xff is 0dB */
    HW_AUDIOOUT_DACVOLUME = BF_OR3(AUDIOOUT_DACVOLUME,
        VOLUME_LEFT(0xff + vol_l), VOLUME_RIGHT(0xff + vol_r), EN_ZCD(1));
}

/* volume in half dB
 * don't check input values */
static void set_hp_vol(int vol_l, int vol_r)
{
    /* minimum is -57.5dB and max is 6dB in DAC mode
     * and -51.5dB / 12dB in Line1 mode */
    int min = input_line1 ? -103 : -115;
    int max = input_line1 ? 24 : 12;

    vol_l = MAX(min, MIN(vol_l, max));
    vol_r = MAX(min, MIN(vol_r, max));
    /* unmute, enable zero cross and set volume.*/
#if IMX233_SUBTARGET >= 3700
    unsigned mstr_zcd = BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD;
#else
    unsigned mstr_zcd = 0;
#endif
    HW_AUDIOOUT_HPVOL = mstr_zcd | BF_OR3(AUDIOOUT_HPVOL, SELECT(input_line1),
        VOL_LEFT(max - vol_l), VOL_RIGHT(max - vol_r));
}

static void apply_volume(void)
{
    /* Two cases: line1 and dac */
    if(input_line1)
    {
        /* In line1 mode, the HP is the only way to adjust the volume */
        set_hp_vol(hp_vol_l, hp_vol_r);
    }
    else
    {
        /* In DAC mode we can use both the HP and the DAC volume.
         * Use the DAC for volume <0 and HP for volume >0 */
        set_dac_vol(MIN(0, hp_vol_l), MIN(0, hp_vol_r));
        set_hp_vol(MAX(0, hp_vol_l), MAX(0, hp_vol_r));
    }
}

void imx233_audioout_set_hp_vol(int vol_l, int vol_r)
{
    hp_vol_l = vol_l;
    hp_vol_r = vol_r;
    apply_volume();
}

void imx233_audioout_set_freq(int fsel)
{
    static struct
    {
        int base_mult;
        int src_hold;
        int src_int;
        int src_frac;
    }dacssr[HW_NUM_FREQ] =
    {
        HW_HAVE_8_([HW_FREQ_8] = { 0x1, 0x3, 0x17, 0xe00 }  ,)
        HW_HAVE_11_([HW_FREQ_11] = { 0x1, 0x3, 0x11, 0x37 } ,)
        HW_HAVE_12_([HW_FREQ_12] = { 0x1, 0x3, 0xf, 0x13ff },)
        HW_HAVE_16_([HW_FREQ_16] = { 0x1, 0x1, 0x17, 0xe00},)
        HW_HAVE_22_([HW_FREQ_22] = { 0x1, 0x1, 0x11, 0x37 },)
        HW_HAVE_24_([HW_FREQ_24] = { 0x1, 0x1, 0xf, 0x13ff },)
        HW_HAVE_32_([HW_FREQ_32] = { 0x1, 0x0, 0x17, 0xe00},)
        HW_HAVE_44_([HW_FREQ_44] = { 0x1, 0x0, 0x11, 0x37 },)
        HW_HAVE_48_([HW_FREQ_48] = { 0x1, 0x0, 0xf, 0x13ff },)
        HW_HAVE_64_([HW_FREQ_64] = { 0x2, 0x0, 0x17, 0xe00},)
        HW_HAVE_88_([HW_FREQ_88] = { 0x2, 0x0, 0x11, 0x37 },)
        HW_HAVE_96_([HW_FREQ_96] = { 0x2, 0x0, 0xf, 0x13ff },)
    };

    HW_AUDIOOUT_DACSRR = BF_OR4(AUDIOOUT_DACSRR,
        SRC_FRAC(dacssr[fsel].src_frac), SRC_INT(dacssr[fsel].src_int),
        SRC_HOLD(dacssr[fsel].src_hold), BASEMULT(dacssr[fsel].base_mult));
    
    #if 0
    /* Select base_mult and src_hold depending on the audio range:
     *     0 < f <= 12000   --> base_mult = 1, src_hold = 3 (div by 4)
     * 12000 < f <= 24000   --> base_mult = 1, src_hold = 1 (div by 2)
     * 24000 < f <= 48000   --> base_mult = 1, src_hold = 0 (div by 1)
     * 48000 < f <= 96000   --> base_mult = 2, src_hold = 0 (mul by 2)
     * 96000 < f <= .....   --> base_mult = 4, src_hold = 0 (mul by 4) */
    int base_mult = 1;
    int src_hold = 0;
    if(f > 96000)
        base_mult = 4;
    else if(f > 48000)
        base_mult = 2;
    else if(f <= 12000)
        src_hold = 3;
    else if(f <= 24000)
        src_hold = 1;
    /* compute the divisor (a tricky to keep to do it with 32-bit words only) */
    int src_int = (750000 * base_mult) / (f * (src_hold + 1));
    int src_frac = ((750000 * base_mult - src_int * f * (src_hold + 1)) << 13) / (f * (src_hold + 1));

    HW_AUDIOOUT_DACSRR = BF_OR4(AUDIOOUT_DACSRR,
        SRC_FRAC(src_frac), SRC_INT(src_int),
        SRC_HOLD(src_hold), BASEMULT(base_mult));
    #endif
}

/* select between DAC and Line1 */
void imx233_audioout_select_hp_input(bool line1)
{
    input_line1 = line1;
    /* reapply volume setting */
    apply_volume();
}

void imx233_audioout_set_3d_effect(int val)
{
    switch(val)
    {
        /* 0 and 1.5dB: off */
        case 0: case 1: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 0); break;
        /* 3dB: low */
        case 2: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 1); break;
        /* 4.5dB: low */
        case 3: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 2); break;
        /* 6dB: low */
        case 4: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 3); break;
        /* others: off */
        default: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 0); break;
    }
}

struct imx233_audioout_info_t imx233_audioout_get_info(void)
{
    struct imx233_audioout_info_t info;
    memset(&info, 0, sizeof(info));
    /* 6*10^6*basemult/(src_frac*8*(src_hold+1)) in Hz */
    info.freq = 60000000 * BF_RD(AUDIOOUT_DACSRR, BASEMULT) / 8 /
        BF_RD(AUDIOOUT_DACSRR, SRC_FRAC) / (1 + BF_RD(AUDIOOUT_DACSRR, SRC_HOLD));
    info.hpselect = BF_RD(AUDIOOUT_HPVOL, SELECT);
    /* convert half-dB to tenth-dB */
    info.dacvol[0] = MAX((int)BF_RD(AUDIOOUT_DACVOLUME, VOLUME_LEFT) - 0xff, -100) * 5;
    info.dacvol[1] = MAX((int)BF_RD(AUDIOOUT_DACVOLUME, VOLUME_RIGHT) - 0xff, -100) * 5;
    info.dacmute[0] = BF_RD(AUDIOOUT_DACVOLUME, MUTE_LEFT);
    info.dacmute[1] = BF_RD(AUDIOOUT_DACVOLUME, MUTE_RIGHT);
    info.hpvol[0] = (info.hpselect ? 120 : 60) - 5 * BF_RD(AUDIOOUT_HPVOL, VOL_LEFT);
    info.hpvol[1] = (info.hpselect ? 120 : 60) - 5 * BF_RD(AUDIOOUT_HPVOL, VOL_RIGHT);
    info.hpmute[0] = info.hpmute[1] = BF_RD(AUDIOOUT_HPVOL, MUTE);
#if IMX233_SUBTARGET >= 3780
    info.spkrvol[0] = info.spkrvol[1] = 155; // volume is fixed to 15.5 dB gain
    info.spkrmute[0] = info.spkrmute[1] = BF_RD(AUDIOOUT_SPEAKERCTRL, MUTE);
    info.spkr = !BF_RD(AUDIOOUT_PWRDN, SPEAKER);
#elif IMX233_SUBTARGET < 3700
    /* convert 2-dB to tenth-dB */
    info.spkrvol[0] = MIN(info.spkrvol[1] = BF_RD(AUDIOOUT_SPKRVOL, VOL), 6) * 20;
    info.spkrmute[0] = info.spkrmute[1] = BF_RD(AUDIOOUT_SPKRVOL, MUTE);
    info.spkr = !BF_RD(AUDIOOUT_PWRDN, SPEAKER);
#else
    /* STMP3700/3770 has not speaker amplifier */
    info.spkrvol[0] = info.spkrvol[1] = 0;
    info.spkrmute[0] = info.spkrmute[1] = true;
    info.spkr = false;
#endif
    info.ss3d = BF_RD(AUDIOOUT_CTRL, SS3D_EFFECT);
    info.ss3d = info.ss3d == 0 ? 0 : 15 * (1 + info.ss3d);
    info.hp = !BF_RD(AUDIOOUT_PWRDN, HEADPHONE);
    info.dac = !BF_RD(AUDIOOUT_PWRDN, DAC);
    info.capless = BF_RD(AUDIOOUT_PWRDN, CAPLESS);
    return info;
}