V4L/DVB (9651): em28xx: Improve audio handling
authorMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 19 Nov 2008 15:01:33 +0000 (12:01 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 29 Dec 2008 19:53:35 +0000 (17:53 -0200)
This patch properly implements audio handling on em28xx. Before this
patch, it was assumed that every device has an Empia 202 audio chip.
However, this is not true.

After this patch, specific AC97 chipset setup and configurations can be
done.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/video/em28xx/em28xx-core.c
drivers/media/video/em28xx/em28xx-video.c
drivers/media/video/em28xx/em28xx.h

index 4de1b2d..2f3257e 100644 (file)
@@ -224,15 +224,70 @@ static int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
 }
 
 /*
+ * em28xx_is_ac97_ready()
+ * Checks if ac97 is ready
+ */
+static int em28xx_is_ac97_ready(struct em28xx *dev)
+{
+       int ret, i;
+
+       /* Wait up to 50 ms for AC97 command to complete */
+       for (i = 0; i < 10; i++, msleep(5)) {
+               ret = em28xx_read_reg(dev, EM28XX_R43_AC97BUSY);
+               if (ret < 0)
+                       return ret;
+
+               if (!(ret & 0x01))
+                       return 0;
+       }
+
+       em28xx_warn("AC97 command still being executed: not handled properly!\n");
+       return -EBUSY;
+}
+
+/*
+ * em28xx_read_ac97()
+ * write a 16 bit value to the specified AC97 address (LSB first!)
+ */
+static int em28xx_read_ac97(struct em28xx *dev, u8 reg)
+{
+       int ret;
+       u8 addr = (reg & 0x7f) | 0x80;
+       u16 val;
+
+       ret = em28xx_is_ac97_ready(dev);
+       if (ret < 0)
+               return ret;
+
+       ret = em28xx_write_regs(dev, EM28XX_R42_AC97ADDR, &addr, 1);
+       if (ret < 0)
+               return ret;
+
+       ret = dev->em28xx_read_reg_req_len(dev, 0, EM28XX_R40_AC97LSB,
+                                          (u8 *)&val, sizeof(val));
+
+       if (ret < 0)
+               return ret;
+       return le16_to_cpu(val);
+}
+
+/*
  * em28xx_write_ac97()
  * write a 16 bit value to the specified AC97 address (LSB first!)
  */
-static int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val)
+static int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val)
 {
-       int ret, i;
+       int ret;
        u8 addr = reg & 0x7f;
+       __le16 value;
+
+       value = cpu_to_le16(val);
+
+       ret = em28xx_is_ac97_ready(dev);
+       if (ret < 0)
+               return ret;
 
-       ret = em28xx_write_regs(dev, EM28XX_R40_AC97LSB, val, 2);
+       ret = em28xx_write_regs(dev, EM28XX_R40_AC97LSB, (u8 *) &value, 2);
        if (ret < 0)
                return ret;
 
@@ -240,25 +295,36 @@ static int em28xx_write_ac97(struct em28xx *dev, u8 reg, u8 *val)
        if (ret < 0)
                return ret;
 
-       /* Wait up to 50 ms for AC97 command to complete */
-       for (i = 0; i < 10; i++) {
-               ret = em28xx_read_reg(dev, EM28XX_R43_AC97BUSY);
-               if (ret < 0)
-                       return ret;
+       return 0;
+}
 
-               if (!(ret & 0x01))
-                       return 0;
-               msleep(5);
+static int set_ac97_em202_input(struct em28xx *dev)
+{
+       int ret;
+       u16 enable  = 0x0808;           /* 12 dB attenuation Left/Right */
+       u16 disable = 0x8808;           /* bit 15 - mute volumme */
+       u16 video, line;
+
+       if (dev->ctl_ainput == EM28XX_AMUX_VIDEO) {
+               video = enable;
+               line = disable;
+       } else {
+               video = disable;
+               line  = enable;
        }
-       em28xx_warn("AC97 command still being executed: not handled properly!\n");
-       return 0;
+
+       /* Sets em202 AC97 mixer registers */
+       ret = em28xx_write_ac97(dev, AC97_VIDEO_VOL, video);
+       if (ret < 0)
+               return ret;
+
+       ret = em28xx_write_ac97(dev, AC97_LINEIN_VOL, line);
+
+       return ret;
 }
 
 static int em28xx_set_audio_source(struct em28xx *dev)
 {
-       static char *enable  = "\x08\x08";
-       static char *disable = "\x08\x88";
-       char *video = enable, *line = disable;
        int ret;
        u8 input;
 
@@ -280,18 +346,8 @@ static int em28xx_set_audio_source(struct em28xx *dev)
                case EM28XX_AMUX_VIDEO:
                        input = EM28XX_AUDIO_SRC_TUNER;
                        break;
-               case EM28XX_AMUX_LINE_IN:
+               default:
                        input = EM28XX_AUDIO_SRC_LINE;
-                       video = disable;
-                       line  = enable;
-                       break;
-               case EM28XX_AMUX_AC97_VIDEO:
-                       input = EM28XX_AUDIO_SRC_LINE;
-                       break;
-               case EM28XX_AMUX_AC97_LINE_IN:
-                       input = EM28XX_AUDIO_SRC_LINE;
-                       video = disable;
-                       line  = enable;
                        break;
                }
        }
@@ -301,33 +357,36 @@ static int em28xx_set_audio_source(struct em28xx *dev)
                return ret;
        msleep(5);
 
-       /* Sets AC97 mixer registers
-          This is seems to be needed, even for non-ac97 configs
-        */
-       ret = em28xx_write_ac97(dev, AC97_VIDEO_VOL, video);
-       if (ret < 0)
-               return ret;
-
-       ret = em28xx_write_ac97(dev, AC97_LINEIN_VOL, line);
+       switch (dev->audio_mode.ac97) {
+       case EM28XX_NO_AC97:
+               break;
+       case EM28XX_AC97_OTHER:
+               /* We don't know how to handle this chip.
+                  Let's hope it is close enough to em202 to work
+                */
+       case EM28XX_AC97_EM202:
+               ret = set_ac97_em202_input(dev);
+               break;
+       }
 
-       return ret;
+       return 0;
 }
 
 int em28xx_audio_analog_set(struct em28xx *dev)
 {
        int ret;
-       char s[2] = { 0x00, 0x00 };
        u8 xclk = 0x07;
 
-       s[0] |= 0x1f - dev->volume;
-       s[1] |= 0x1f - dev->volume;
+       if (!dev->audio_mode.has_audio)
+               return 0;
 
-       /* Mute */
-       s[1] |= 0x80;
-       ret = em28xx_write_ac97(dev, AC97_MASTER_VOL, s);
+       if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+               /* Mute */
+               ret = em28xx_write_ac97(dev, AC97_MASTER_VOL, 0x8000);
 
-       if (ret < 0)
-               return ret;
+               if (ret < 0)
+                       return ret;
+       }
 
        if (dev->has_12mhz_i2s)
                xclk |= 0x20;
@@ -343,15 +402,113 @@ int em28xx_audio_analog_set(struct em28xx *dev)
        /* Selects the proper audio input */
        ret = em28xx_set_audio_source(dev);
 
-       /* Unmute device */
-       if (!dev->mute)
-               s[1] &= ~0x80;
-       ret = em28xx_write_ac97(dev, AC97_MASTER_VOL, s);
+       /* Sets volume */
+       if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+               int vol;
+
+               /* LSB: left channel - both channels with the same level */
+               vol = (0x1f - dev->volume) | ((0x1f - dev->volume) << 8);
+
+               /* Mute device, if needed */
+               if (dev->mute)
+                       vol |= 0x8000;
+
+               /* Sets volume */
+               ret = em28xx_write_ac97(dev, AC97_MASTER_VOL, vol);
+       }
 
        return ret;
 }
 EXPORT_SYMBOL_GPL(em28xx_audio_analog_set);
 
+int em28xx_audio_setup(struct em28xx *dev)
+{
+       int vid1, vid2, feat, cfg;
+
+       if (dev->chip_id == CHIP_ID_EM2874) {
+               /* Digital only device - don't load any alsa module */
+               dev->audio_mode.has_audio = 0;
+               dev->has_audio_class = 0;
+               dev->has_alsa_audio = 0;
+               return 0;
+       }
+
+       /* If device doesn't support Usb Audio Class, use vendor class */
+       if (!dev->has_audio_class)
+               dev->has_alsa_audio = 1;
+
+       dev->audio_mode.has_audio = 1;
+
+       /* See how this device is configured */
+       cfg = em28xx_read_reg(dev, EM28XX_R00_CHIPCFG);
+       if (cfg < 0)
+               cfg = EM28XX_CHIPCFG_AC97; /* Be conservative */
+       else
+               em28xx_info("Config register raw data: 0x%02x\n", cfg);
+
+       if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) ==
+                   EM28XX_CHIPCFG_I2S_3_SAMPRATES) {
+               em28xx_info("I2S Audio (3 sample rates)\n");
+               dev->audio_mode.i2s_3rates = 1;
+       }
+       if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) ==
+                   EM28XX_CHIPCFG_I2S_5_SAMPRATES) {
+               em28xx_info("I2S Audio (5 sample rates)\n");
+               dev->audio_mode.i2s_5rates = 1;
+       }
+
+       if (!(cfg & EM28XX_CHIPCFG_AC97)) {
+               dev->audio_mode.ac97 = EM28XX_NO_AC97;
+               goto init_audio;
+       }
+
+       dev->audio_mode.ac97 = EM28XX_AC97_OTHER;
+
+       vid1 = em28xx_read_ac97(dev, AC97_VENDOR_ID1);
+       if (vid1 < 0) {
+               /* Device likely doesn't support AC97 */
+               em28xx_warn("AC97 chip type couldn't be determined\n");
+               goto init_audio;
+       }
+
+       vid2 = em28xx_read_ac97(dev, AC97_VENDOR_ID2);
+       if (vid2 < 0)
+               goto init_audio;
+
+       dev->audio_mode.ac97_vendor_id1 = vid1;
+       dev->audio_mode.ac97_vendor_id2 = vid2;
+       em28xx_warn("AC97 vendor ID = %04x:%04x\n", vid1, vid2);
+
+       feat = em28xx_read_ac97(dev, AC97_RESET);
+       if (feat < 0)
+               goto init_audio;
+
+       dev->audio_mode.ac97_feat = feat;
+       em28xx_warn("AC97 features = 0x%04x\n", feat);
+
+       if ((vid1 == 0xffff) && (vid2 == 0xffff) && (feat == 0x6a90))
+               dev->audio_mode.ac97 = EM28XX_AC97_EM202;
+
+init_audio:
+       /* Reports detected AC97 processor */
+       switch (dev->audio_mode.ac97) {
+       case EM28XX_NO_AC97:
+               em28xx_info("No AC97 audio processor\n");
+               break;
+       case EM28XX_AC97_EM202:
+               em28xx_info("Empia 202 AC97 audio processor detected\n");
+               break;
+       case EM28XX_AC97_OTHER:
+               em28xx_warn("Unknown AC97 audio processor detected!\n");
+               break;
+       default:
+               break;
+       }
+
+       return em28xx_audio_analog_set(dev);
+}
+EXPORT_SYMBOL_GPL(em28xx_audio_setup);
+
 int em28xx_colorlevels_set_default(struct em28xx *dev)
 {
        em28xx_write_regs(dev, EM28XX_R20_YGAIN, "\x10", 1);    /* contrast */
index 1e26061..6b7d44a 100644 (file)
@@ -1931,53 +1931,6 @@ static struct video_device *em28xx_vdev_init(struct em28xx *dev,
        return vfd;
 }
 
-int em28xx_supports_audio_extension(struct em28xx *dev)
-{
-       int rc;
-
-       /* The chip dictates whether we support the Empia analog audio
-          extension */
-       switch (dev->chip_id) {
-       case CHIP_ID_EM2874:
-               /* Digital only device - no analog support */
-               dev->audio_mode = EM28XX_NO_AUDIO;
-               return 0;
-       case CHIP_ID_EM2860:
-       case CHIP_ID_EM2883:
-       default:
-               /* See how this device is configured */
-               rc = em28xx_read_reg(dev, EM28XX_R00_CHIPCFG);
-               if (rc & EM28XX_CHIPCFG_VENDOR_AUDIO) {
-                       switch(rc & EM28XX_CHIPCFG_AUDIOMASK) {
-                       case EM28XX_CHIPCFG_AC97:
-                               em28xx_info("AC97 audio (5 sample rates)\n");
-                               dev->audio_mode = EM28XX_AC97;
-                               break;
-                       case EM28XX_CHIPCFG_I2S_3_SAMPRATES:
-                               em28xx_info("I2S Audio (3 sample rates)\n");
-                               dev->audio_mode = EM28XX_I2S_3_SAMPLE_RATES;
-                               break;
-                       case EM28XX_CHIPCFG_I2S_5_SAMPRATES:
-                               em28xx_info("I2S Audio (5 sample rates)\n");
-                               dev->audio_mode = EM28XX_I2S_5_SAMPLE_RATES;
-                               break;
-                       default:
-                               em28xx_info("No audio support detected\n");
-                               dev->audio_mode = EM28XX_NO_AUDIO;
-                               return 0;
-                       }
-               } else {
-                       em28xx_info("USB Audio class device\n");
-                       return 0;
-               }
-               /* The em28xx audio extension needs to be loaded */
-               return 1;
-       }
-
-       /* We should never reach this point */
-       return 0;
-}
-
 static int register_analog_devices(struct em28xx *dev)
 {
        int ret;
@@ -2080,11 +2033,10 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
        em28xx_card_setup(dev);
 
        /* Configure audio */
-       errCode = em28xx_audio_analog_set(dev);
+       errCode = em28xx_audio_setup(dev);
        if (errCode < 0) {
-               em28xx_errdev("%s: em28xx_audio_analog_set - errCode [%d]!\n",
+               em28xx_errdev("%s: Error while setting audio - errCode [%d]!\n",
                        __func__, errCode);
-               return errCode;
        }
 
        /* configure the device */
@@ -2318,17 +2270,6 @@ static int em28xx_usb_probe(struct usb_interface *interface,
 
        em28xx_info("Found %s\n", em28xx_boards[dev->model].name);
 
-       if (dev->has_audio_class == 0) {
-               /* We don't have a USB audio class, let's see if we support
-                  ALSA Audio */
-               dev->has_alsa_audio = em28xx_supports_audio_extension(dev);
-               if (dev->has_alsa_audio)
-                       printk(KERN_INFO DRIVER_NAME " supports alsa audio\n");
-       } else {
-               printk(KERN_INFO DRIVER_NAME " has usb audio class\n");
-       }
-
-
        /* save our data pointer in this interface device */
        usb_set_intfdata(interface, dev);
 
index f47c8d3..3e8e13a 100644 (file)
@@ -256,18 +256,28 @@ enum enum28xx_itype {
        EM28XX_RADIO,
 };
 
-enum em28xx_audio_mode {
-       EM28XX_NO_AUDIO,
-       EM28XX_I2S_3_SAMPLE_RATES,
-       EM28XX_I2S_5_SAMPLE_RATES,
-       EM28XX_AC97,
+enum em28xx_ac97_mode {
+       EM28XX_NO_AC97 = 0,
+       EM28XX_AC97_EM202,
+       EM28XX_AC97_OTHER,
+};
+
+struct em28xx_audio_mode {
+       enum em28xx_ac97_mode ac97;
+
+       u16 ac97_feat;
+       u16 ac97_vendor_id1;
+       u16 ac97_vendor_id2;
+
+       unsigned int has_audio:1;
+
+       unsigned int i2s_3rates:1;
+       unsigned int i2s_5rates:1;
 };
 
 enum em28xx_amux {
        EM28XX_AMUX_VIDEO,
        EM28XX_AMUX_LINE_IN,
-       EM28XX_AMUX_AC97_VIDEO,
-       EM28XX_AMUX_AC97_LINE_IN,
 };
 
 struct em28xx_input {
@@ -410,7 +420,7 @@ struct em28xx {
        u32 i2s_speed;          /* I2S speed for audio digital stream */
 
        enum em28xx_decoder decoder;
-       enum em28xx_audio_mode audio_mode;
+       struct em28xx_audio_mode audio_mode;
 
        int tuner_type;         /* type of the tuner */
        int tuner_addr;         /* tuner address */
@@ -531,6 +541,7 @@ int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
                          int len);
 int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len);
 int em28xx_audio_analog_set(struct em28xx *dev);
+int em28xx_audio_setup(struct em28xx *dev);
 
 int em28xx_colorlevels_set_default(struct em28xx *dev);
 int em28xx_capture_start(struct em28xx *dev, int start);