D2: Beginnings of a WM8985 driver (based on WM8758, using EQ1 and EQ5 as HW treble/bass). Untested, but harmless.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17057 a1c6a512-1295-4272-9138-f99709370657
diff --git a/firmware/drivers/audio/wm8985.c b/firmware/drivers/audio/wm8985.c
index ad19a58..163e907 100644
--- a/firmware/drivers/audio/wm8985.c
+++ b/firmware/drivers/audio/wm8985.c
@@ -7,8 +7,6 @@
  *                     \/            \/     \/    \/            \/
  * $Id$
  *
- * Stubs for WM8985 audio codec, (unwisely?) based on 8975 driver.
- *
  * All files in this archive are subject to the GNU General Public License.
  * See the file COPYING in the source tree root for full license agreement.
  *
@@ -25,117 +23,325 @@
 #include "audiohw.h"
 #include "i2s.h"
 
-/* TODO: fix these values, they're copied straight from WM8975 */
+/* Register addresses as per datasheet Rev.4.4 */
+#define RESET       0x00
+#define PWRMGMT1    0x01
+#define PWRMGMT2    0x02
+#define PWRMGMT3    0x03
+#define AINTFCE     0x04
+#define COMPAND     0x05
+#define CLKGEN      0x06
+#define SRATECTRL   0x07
+#define GPIOCTL     0x08
+#define JACKDETECT0 0x09
+#define DACCTRL     0x0a
+#define LDACVOL     0x0b
+#define RDACVOL     0x0c
+#define JACKDETECT1 0x0d
+#define ADCCTL      0x0e
+#define LADCVOL     0x0f
+#define RADCVOL     0x10
+
+#define EQ1         0x12
+#define EQ2         0x13
+#define EQ3         0x14
+#define EQ4         0x15
+#define EQ5         0x16
+#define EQ_GAIN_MASK       0x001f
+#define EQ_CUTOFF_MASK     0x0060
+#define EQ_GAIN_VALUE(x)   (((-x) + 12) & 0x1f)
+#define EQ_CUTOFF_VALUE(x) ((((x) - 1) & 0x03) << 5)
+
+#define CLASSDCTL   0x17
+#define DACLIMIT1   0x18
+#define DACLIMIT2   0x19
+#define NOTCH1      0x1b
+#define NOTCH2      0x1c
+#define NOTCH3      0x1d
+#define NOTCH4      0x1e
+#define ALCCTL1     0x20
+#define ALCCTL2     0x21
+#define ALCCTL3     0x22
+#define NOISEGATE   0x23
+#define PLLN        0x24
+#define PLLK1       0x25
+#define PLLK2       0x26
+#define PLLK3       0x27
+#define THREEDCTL   0x29
+#define OUT4ADC     0x2a
+#define BEEPCTRL    0x2b
+#define INCTRL      0x2c
+#define LINPGAGAIN  0x2d
+#define RINPGAGAIN  0x2e
+#define LADCBOOST   0x2f
+#define RADCBOOST   0x30
+#define OUTCTRL     0x31
+#define LOUTMIX     0x32
+#define ROUTMIX     0x33
+#define LOUT1VOL    0x34
+#define ROUT1VOL    0x35
+#define LOUT2VOL    0x36
+#define ROUT2VOL    0x37
+#define OUT3MIX     0x38
+#define OUT4MIX     0x39
+#define BIASCTL     0x3d
+
+/* Register settings for the supported samplerates: */
+#define WM8985_8000HZ      0x4d
+#define WM8985_12000HZ     0x61
+#define WM8985_16000HZ     0x55
+#define WM8985_22050HZ     0x77
+#define WM8985_24000HZ     0x79
+#define WM8985_32000HZ     0x59
+#define WM8985_44100HZ     0x63
+#define WM8985_48000HZ     0x41
+#define WM8985_88200HZ     0x7f
+#define WM8985_96000HZ     0x5d
+
 const struct sound_settings_info audiohw_settings[] = {
-    [SOUND_VOLUME]        = {"dB", 0,  1, -74,   6, -25},
-    [SOUND_BASS]          = {"dB", 0,  1,  -6,   9,   0},
-    [SOUND_TREBLE]        = {"dB", 0,  1,  -6,   9,   0},
+    [SOUND_VOLUME]        = {"dB", 0,  1, -58,   6, -25},
+    [SOUND_BASS]          = {"dB", 0,  1, -12,  12,   0},
+    [SOUND_TREBLE]        = {"dB", 0,  1, -12,  12,   0},
     [SOUND_BALANCE]       = {"%",  0,  1,-100, 100,   0},
     [SOUND_CHANNELS]      = {"",   0,  1,   0,   5,   0},
     [SOUND_STEREO_WIDTH]  = {"%",  0,  5,   0, 250, 100},
     [SOUND_LEFT_GAIN]     = {"dB", 1,  1,-128,  96,   0},
     [SOUND_RIGHT_GAIN]    = {"dB", 1,  1,-128,  96,   0},
     [SOUND_MIC_GAIN]      = {"dB", 1,  1,-128, 108,  16},
+    [SOUND_BASS_CUTOFF]   = {"",   0,  1,   1,   4,   1},
+    [SOUND_TREBLE_CUTOFF] = {"",   0,  1,   1,   4,   1},
 };
 
-/* convert tenth of dB volume to master volume register value */
+/* shadow registers */
+unsigned int eq1_reg;
+unsigned int eq5_reg;
+
+/* convert tenth of dB volume (-57..6) to master volume register value */
 int tenthdb2master(int db)
 {
-    #warning function not implemented
-  
-    (void)db;
-    return 0;
+    /* +6 to -57dB in 1dB steps == 64 levels = 6 bits */
+    /* 0111111 == +6dB  (0x3f) = 63) */
+    /* 0111001 == 0dB   (0x39) = 57) */
+    /* 0000001 == -56dB (0x01) = */
+    /* 0000000 == -57dB (0x00) */
+
+    /* 1000000 == Mute (0x40) */
+
+    if (db < VOLUME_MIN) {
+        return 0x40;
+    } else {
+        return((db/10)+57);
+    }
+}
+
+/* convert tenth of dB volume (-780..0) to mixer volume register value */
+int tenthdb2mixer(int db)
+{
+    if (db < -660)                 /* 1.5 dB steps */
+        return (2640 - db) / 15;
+    else if (db < -600)            /* 0.75 dB steps */
+        return (990 - db) * 2 / 15;
+    else if (db < -460)            /* 0.5 dB steps */
+        return (460 - db) / 5;
+    else                           /* 0.25 dB steps */
+        return -db * 2 / 5;
 }
 
 /* Silently enable / disable audio output */
 void audiohw_enable_output(bool enable)
 {
-    #warning function not implemented
-  
-    (void)enable;
+    if (enable)
+    {
+        /* TODO: reset the I2S controller into known state */
+        //i2s_reset();
+
+        /* TODO: Review the power-up sequence to prevent pops (see datasheet) */
+
+        wmcodec_write(RESET, 0x1ff);    /*Reset*/
+
+        wmcodec_write(PWRMGMT1, 0x2b);
+        wmcodec_write(PWRMGMT2, 0x180);
+        wmcodec_write(PWRMGMT3, 0x6f);
+
+        wmcodec_write(AINTFCE, 0x10);
+        wmcodec_write(CLKCTRL, 0x49);
+
+        wmcodec_write(OUTCTRL, 1);
+
+        /* The iPod can handle multiple frequencies, but fix at 44.1KHz
+           for now */
+        audiohw_set_sample_rate(WM8985_44100HZ);
+
+        wmcodec_write(LOUTMIX,0x1); /* Enable mixer */
+        wmcodec_write(ROUTMIX,0x1); /* Enable mixer */
+        audiohw_mute(0);
+    } else {
+        audiohw_mute(1);
+    }
 }
 
 void audiohw_set_master_vol(int vol_l, int vol_r)
 {
-    #warning function not implemented
-
-    (void)vol_l;
-    (void)vol_r;
+    /* OUT1 */
+    wmcodec_write(LOUT1VOL, 0x080 | vol_l);
+    wmcodec_write(ROUT1VOL, 0x180 | vol_r);
 }
 
 void audiohw_set_lineout_vol(int vol_l, int vol_r)
 {
-     #warning function not implemented
+    /* OUT2 */
+    wmcodec_write(LOUT2VOL, vol_l);
+    wmcodec_write(ROUT2VOL, 0x100 | vol_r);
+}
 
-    (void)vol_l;
-    (void)vol_r;
+void audiohw_set_mixer_vol(int channel1, int channel2)
+{
+    (void)channel1;
+    (void)channel2;
 }
 
 void audiohw_set_bass(int value)
 {
-    #warning function not implemented
+    eq1_reg = (eq1_reg & ~EQ_GAIN_MASK) | EQ_GAIN_VALUE(value);
+    wmcodec_write(EQ1, 0x100 | eq1_reg);
+}
 
-    (void)value;
+void audiohw_set_bass_cutoff(int value)
+{
+    eq1_reg = (eq1_reg & ~EQ_CUTOFF_MASK) | EQ_CUTOFF_VALUE(value);
+    wmcodec_write(EQ1, 0x100 | eq1_reg);
 }
 
 void audiohw_set_treble(int value)
 {
-    #warning function not implemented
+    eq5_reg = (eq5_reg & ~EQ_GAIN_MASK) | EQ_GAIN_VALUE(value);
+    wmcodec_write(EQ5, eq5_reg);
+}
 
-    (void)value;
+void audiohw_set_treble_cutoff(int value)
+{
+    eq5_reg = (eq5_reg & ~EQ_CUTOFF_MASK) | EQ_CUTOFF_VALUE(value);
+    wmcodec_write(EQ5, eq5_reg);
 }
 
 void audiohw_mute(bool mute)
 {
-    #warning function not implemented
-
-    (void)mute;
+    if (mute)
+    {
+        /* Set DACMU = 1 to soft-mute the audio DACs. */
+        wmcodec_write(DACCTRL, 0x40);
+    } else {
+        /* Set DACMU = 0 to soft-un-mute the audio DACs. */
+        wmcodec_write(DACCTRL, 0x0);
+    }
 }
 
+/* Nice shutdown of WM8758 codec */
 void audiohw_close(void)
 {
-    #warning function not implemented
+    audiohw_mute(1);
+
+    wmcodec_write(PWRMGMT3, 0x0);
+
+    wmcodec_write(PWRMGMT1, 0x0);
+
+    wmcodec_write(PWRMGMT2, 0x40);
 }
 
+/* Change the order of the noise shaper, 5th order is recommended above 32kHz */
 void audiohw_set_nsorder(int order)
 {
-    #warning function not implemented
-
     (void)order;
 }
 
 /* Note: Disable output before calling this function */
 void audiohw_set_sample_rate(int sampling_control)
 {
-    #warning function not implemented
-
+    /**** We force 44.1KHz for now. ****/
     (void)sampling_control;
+
+    /* set clock div */
+    wmcodec_write(CLKCTRL, 1 | (0 << 2) | (2 << 5));
+
+    /* setup PLL for MHZ=11.2896 */
+    wmcodec_write(PLLN, (1 << 4) | 0x7);
+    wmcodec_write(PLLK1, 0x21);
+    wmcodec_write(PLLK2, 0x161);
+    wmcodec_write(PLLK3, 0x26);
+
+    /* set clock div */
+    wmcodec_write(CLKCTRL, 1 | (1 << 2) | (2 << 5) | (1 << 8));
+
+    /* set srate */
+    wmcodec_write(SRATECTRL, (0 << 1));
 }
 
 void audiohw_enable_recording(bool source_mic)
 {
-    #warning function not implemented
+    (void)source_mic; /* We only have a line-in (I think) */
 
-    (void)source_mic;
-}
- 
-void audiohw_disable_recording(void)
-{
-    #warning function not implemented
+    /* TODO: reset the I2S controller into known state */
+    //i2s_reset();
+
+    wmcodec_write(RESET, 0x1ff);    /*Reset*/
+
+    wmcodec_write(PWRMGMT1, 0x2b);
+    wmcodec_write(PWRMGMT2, 0x18f);  /* Enable ADC - 0x0c enables left/right PGA input, and 0x03 turns on power to the ADCs */
+    wmcodec_write(PWRMGMT3, 0x6f);
+
+    wmcodec_write(AINTFCE, 0x10);
+    wmcodec_write(CLKCTRL, 0x49);
+
+    wmcodec_write(OUTCTRL, 1);
+
+    /* The iPod can handle multiple frequencies, but fix at 44.1KHz
+       for now */
+    audiohw_set_sample_rate(WM8985_44100HZ);
+
+    wmcodec_write(INCTRL,0x44);  /* Connect L2 and R2 inputs */
+
+    /* Set L2/R2_2BOOSTVOL to 0db (bits 4-6) */
+    /* 000 = disabled
+       001 = -12dB
+       010 = -9dB
+       011 = -6dB
+       100 = -3dB
+       101 = 0dB
+       110 = 3dB
+       111 = 6dB
+    */
+    wmcodec_write(LADCBOOST,0x50);
+    wmcodec_write(RADCBOOST,0x50);
+
+    /* Set L/R input PGA Volume to 0db */
+    //    wm8758_write(LINPGAVOL,0x3f);
+    //    wm8758_write(RINPGAVOL,0x13f);
+
+    /* Enable monitoring */
+    wmcodec_write(LOUTMIX,0x17); /* Enable output mixer - BYPL2LMIX @ 0db*/
+    wmcodec_write(ROUTMIX,0x17); /* Enable output mixer - BYPR2RMIX @ 0db*/
+
+    audiohw_mute(0);
 }
 
-void audiohw_set_recvol(int left, int right, int type)
-{
-    #warning function not implemented
+void audiohw_disable_recording(void) {
+    audiohw_mute(1);
+
+    wmcodec_write(PWRMGMT3, 0x0);
+
+    wmcodec_write(PWRMGMT1, 0x0);
+
+    wmcodec_write(PWRMGMT2, 0x40);
+}
+
+void audiohw_set_recvol(int left, int right, int type) {
 
     (void)left;
     (void)right;
     (void)type;
 }
 
-void audiohw_set_monitor(bool enable)
-{
-    #warning function not implemented
+void audiohw_set_monitor(bool enable) {
 
     (void)enable;
 }
diff --git a/firmware/export/audiohw.h b/firmware/export/audiohw.h
index ca70681..a4ff75c 100644
--- a/firmware/export/audiohw.h
+++ b/firmware/export/audiohw.h
@@ -69,7 +69,7 @@
     SOUND_RIGHT_GAIN,
     SOUND_MIC_GAIN,
 #endif
-#ifdef HAVE_WM8758
+#if defined(HAVE_WM8758) || defined(HAVE_WM8985)
     SOUND_BASS_CUTOFF,
     SOUND_TREBLE_CUTOFF,
 #endif
diff --git a/firmware/export/config-cowond2.h b/firmware/export/config-cowond2.h
index 78becb3..74cf90e 100644
--- a/firmware/export/config-cowond2.h
+++ b/firmware/export/config-cowond2.h
@@ -83,8 +83,8 @@
 /* The D2 uses a WM8985 codec */
 #define HAVE_WM8985
 
-/* There is no hardware tone control */
-#define HAVE_SW_TONE_CONTROLS
+/* Use WM8985 EQ1 & EQ5 as hardware tone controls */
+/* #define HAVE_SW_TONE_CONTROLS */
 
 /* Define this for LCD backlight available */
 #define HAVE_BACKLIGHT
diff --git a/firmware/export/wm8985.h b/firmware/export/wm8985.h
index a12d7cd..8fa5842 100644
--- a/firmware/export/wm8985.h
+++ b/firmware/export/wm8985.h
@@ -21,22 +21,20 @@
 #define _WM8985_H
 
 /* volume/balance/treble/bass interdependency */
-#define VOLUME_MIN -730
+#define VOLUME_MIN -570
 #define VOLUME_MAX  60
 
 extern int tenthdb2master(int db);
+extern int tenthdb2mixer(int db);
 
 extern void audiohw_set_master_vol(int vol_l, int vol_r);
 extern void audiohw_set_lineout_vol(int vol_l, int vol_r);
+extern void audiohw_set_mixer_vol(int channel1, int channel2);
 extern void audiohw_set_bass(int value);
+extern void audiohw_set_bass_cutoff(int value);
 extern void audiohw_set_treble(int value);
+extern void audiohw_set_treble_cutoff(int value);
 extern void audiohw_set_nsorder(int order);
 extern void audiohw_set_sample_rate(int sampling_control);
 
-/* Register addresses */
-// .. tbc
-
-/* Register settings for the supported samplerates: */
-// .. tbc
-
 #endif /* _WM8985_H */
diff --git a/firmware/sound.c b/firmware/sound.c
index b559fe2..123675c 100644
--- a/firmware/sound.c
+++ b/firmware/sound.c
@@ -244,7 +244,7 @@
  * the prescaler stay at 0 for these unless SW tone controls are in use */
 #if defined(HAVE_SW_TONE_CONTROLS) || !(defined(HAVE_WM8975) \
     || defined(HAVE_WM8731) || defined(HAVE_WM8721) || defined(HAVE_WM8751) \
-    || defined(HAVE_WM8758))
+    || defined(HAVE_WM8758) || defined(HAVE_WM8985))
 
     prescale = MAX(current_bass, current_treble);
     if (prescale < 0)
@@ -453,7 +453,7 @@
     audiohw_set_bass(value);
     set_prescaled_volume();
 #elif defined HAVE_WM8975 || defined HAVE_WM8758 || defined(HAVE_UDA1380) \
-    || defined HAVE_WM8731 || defined(HAVE_WM8721)
+    || defined HAVE_WM8731 || defined(HAVE_WM8721) || defined(HAVE_WM8985)
     current_bass = value * 10;
     audiohw_set_bass(value);
     set_prescaled_volume();
@@ -483,7 +483,7 @@
     current_treble = value;
     set_prescaled_volume();
 #elif defined(HAVE_WM8975) || defined(HAVE_WM8758) || defined(HAVE_UDA1380) \
-   || defined(HAVE_WM8731) || defined(HAVE_WM8721)
+   || defined(HAVE_WM8731) || defined(HAVE_WM8721) || defined(HAVE_WM8985)
     audiohw_set_treble(value);
     current_treble = value * 10;
     set_prescaled_volume();
@@ -518,7 +518,7 @@
 #endif
 }
 
-#ifdef HAVE_WM8758
+#if defined(HAVE_WM8758) || defined(HAVE_WM8985)
 void sound_set_bass_cutoff(int value)
 {
     if(!audio_is_initialized)
@@ -697,7 +697,7 @@
 }
 #endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */
 
-#ifdef HAVE_WM8758
+#if defined(HAVE_WM8758) || defined(HAVE_WM8985)
 void sound_set_bass_cutoff(int value)
 {
     (void) value;