sound: oxygen: handle cards with broken EEPROM
authorClemens Ladisch <clemens@ladisch.de>
Thu, 19 Feb 2009 07:42:44 +0000 (08:42 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 19 Feb 2009 09:22:25 +0000 (10:22 +0100)
Under as yet unknown circumstances, the first word of the sound card's
EEPROM gets overwritten.  When this has happened, we cannot rely on the
subsystem IDs that the kernel reads from the PCI configuration
registers.  Instead, we read the IDs directly from the EEPROM and do the
ID matching manually.

Because the model-specific driver cannot determine the model before
calling oxygen_pci_probe(), that function now gets a get_model()
callback as parameter.  The customizing of the model structure, which
was formerly done by the probe() callback, also has moved into
get_model().

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/oxygen/hifier.c
sound/pci/oxygen/oxygen.c
sound/pci/oxygen/oxygen.h
sound/pci/oxygen/oxygen_io.c
sound/pci/oxygen/oxygen_lib.c
sound/pci/oxygen/virtuoso.c

index cc98bad..84ef131 100644 (file)
@@ -45,6 +45,7 @@ MODULE_PARM_DESC(enable, "enable card");
 static struct pci_device_id hifier_ids[] __devinitdata = {
        { OXYGEN_PCI_SUBID(0x14c3, 0x1710) },
        { OXYGEN_PCI_SUBID(0x14c3, 0x1711) },
+       { OXYGEN_PCI_SUBID_BROKEN_EEPROM },
        { }
 };
 MODULE_DEVICE_TABLE(pci, hifier_ids);
@@ -172,6 +173,13 @@ static const struct oxygen_model model_hifier = {
        .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
 };
 
+static int __devinit get_hifier_model(struct oxygen *chip,
+                                     const struct pci_device_id *id)
+{
+       chip->model = model_hifier;
+       return 0;
+}
+
 static int __devinit hifier_probe(struct pci_dev *pci,
                                  const struct pci_device_id *pci_id)
 {
@@ -184,7 +192,8 @@ static int __devinit hifier_probe(struct pci_dev *pci,
                ++dev;
                return -ENOENT;
        }
-       err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE, &model_hifier, 0);
+       err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+                              hifier_ids, get_hifier_model);
        if (err >= 0)
                ++dev;
        return err;
index 12b6c21..f2c37f3 100644 (file)
@@ -293,29 +293,10 @@ static void set_ak5385_params(struct oxygen *chip,
 
 static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
 
-static int generic_probe(struct oxygen *chip, unsigned long driver_data)
-{
-       if (driver_data == MODEL_MERIDIAN) {
-               chip->model.init = meridian_init;
-               chip->model.resume = meridian_resume;
-               chip->model.set_adc_params = set_ak5385_params;
-               chip->model.device_config = PLAYBACK_0_TO_I2S |
-                                           PLAYBACK_1_TO_SPDIF |
-                                           CAPTURE_0_FROM_I2S_2 |
-                                           CAPTURE_1_FROM_SPDIF;
-       }
-       if (driver_data == MODEL_MERIDIAN || driver_data == MODEL_HALO) {
-               chip->model.misc_flags = OXYGEN_MISC_MIDI;
-               chip->model.device_config |= MIDI_OUTPUT | MIDI_INPUT;
-       }
-       return 0;
-}
-
 static const struct oxygen_model model_generic = {
        .shortname = "C-Media CMI8788",
        .longname = "C-Media Oxygen HD Audio",
        .chip = "CMI8788",
-       .probe = generic_probe,
        .init = generic_init,
        .cleanup = generic_cleanup,
        .resume = generic_resume,
@@ -340,6 +321,29 @@ static const struct oxygen_model model_generic = {
        .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
 };
 
+static int __devinit get_oxygen_model(struct oxygen *chip,
+                                     const struct pci_device_id *id)
+{
+       chip->model = model_generic;
+       switch (id->driver_data) {
+       case MODEL_MERIDIAN:
+               chip->model.init = meridian_init;
+               chip->model.resume = meridian_resume;
+               chip->model.set_adc_params = set_ak5385_params;
+               chip->model.device_config = PLAYBACK_0_TO_I2S |
+                                           PLAYBACK_1_TO_SPDIF |
+                                           CAPTURE_0_FROM_I2S_2 |
+                                           CAPTURE_1_FROM_SPDIF;
+               break;
+       }
+       if (id->driver_data == MODEL_MERIDIAN ||
+           id->driver_data == MODEL_HALO) {
+               chip->model.misc_flags = OXYGEN_MISC_MIDI;
+               chip->model.device_config |= MIDI_OUTPUT | MIDI_INPUT;
+       }
+       return 0;
+}
+
 static int __devinit generic_oxygen_probe(struct pci_dev *pci,
                                          const struct pci_device_id *pci_id)
 {
@@ -353,7 +357,7 @@ static int __devinit generic_oxygen_probe(struct pci_dev *pci,
                return -ENOENT;
        }
        err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
-                              &model_generic, pci_id->driver_data);
+                              oxygen_ids, get_oxygen_model);
        if (err >= 0)
                ++dev;
        return err;
index 268bff4..c500d48 100644 (file)
@@ -49,7 +49,13 @@ enum {
        .subvendor = sv, \
        .subdevice = sd
 
+#define BROKEN_EEPROM_DRIVER_DATA ((unsigned long)-1)
+#define OXYGEN_PCI_SUBID_BROKEN_EEPROM \
+       OXYGEN_PCI_SUBID(PCI_VENDOR_ID_CMEDIA, 0x8788), \
+       .driver_data = BROKEN_EEPROM_DRIVER_DATA
+
 struct pci_dev;
+struct pci_device_id;
 struct snd_card;
 struct snd_pcm_substream;
 struct snd_pcm_hardware;
@@ -62,7 +68,6 @@ struct oxygen_model {
        const char *shortname;
        const char *longname;
        const char *chip;
-       int (*probe)(struct oxygen *chip, unsigned long driver_data);
        void (*init)(struct oxygen *chip);
        int (*control_filter)(struct snd_kcontrol_new *template);
        int (*mixer_init)(struct oxygen *chip);
@@ -82,6 +87,7 @@ struct oxygen_model {
        void (*ac97_switch)(struct oxygen *chip,
                            unsigned int reg, unsigned int mute);
        const unsigned int *dac_tlv;
+       unsigned long private_data;
        size_t model_data_size;
        unsigned int device_config;
        u8 dac_channels;
@@ -134,8 +140,11 @@ struct oxygen {
 
 int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
                     struct module *owner,
-                    const struct oxygen_model *model,
-                    unsigned long driver_data);
+                    const struct pci_device_id *ids,
+                    int (*get_model)(struct oxygen *chip,
+                                     const struct pci_device_id *id
+                                    )
+                   );
 void oxygen_pci_remove(struct pci_dev *pci);
 #ifdef CONFIG_PM
 int oxygen_pci_suspend(struct pci_dev *pci, pm_message_t state);
@@ -180,6 +189,8 @@ void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data);
 void oxygen_reset_uart(struct oxygen *chip);
 void oxygen_write_uart(struct oxygen *chip, u8 data);
 
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index);
+
 static inline void oxygen_set_bits8(struct oxygen *chip,
                                    unsigned int reg, u8 value)
 {
index 3126c4b..05f48ef 100644 (file)
@@ -254,3 +254,18 @@ void oxygen_write_uart(struct oxygen *chip, u8 data)
        _write_uart(chip, 0, data);
 }
 EXPORT_SYMBOL(oxygen_write_uart);
+
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index)
+{
+       unsigned int timeout;
+
+       oxygen_write8(chip, OXYGEN_EEPROM_CONTROL,
+                     index | OXYGEN_EEPROM_DIR_READ);
+       for (timeout = 0; timeout < 100; ++timeout) {
+               udelay(1);
+               if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS)
+                     & OXYGEN_EEPROM_BUSY))
+                       break;
+       }
+       return oxygen_read16(chip, OXYGEN_EEPROM_DATA);
+}
index 516d94a..d83c3a9 100644 (file)
@@ -244,6 +244,34 @@ static void oxygen_proc_init(struct oxygen *chip)
 #define oxygen_proc_init(chip)
 #endif
 
+static const struct pci_device_id *
+oxygen_search_pci_id(struct oxygen *chip, const struct pci_device_id ids[])
+{
+       u16 subdevice;
+
+       /*
+        * Make sure the EEPROM pins are available, i.e., not used for SPI.
+        * (This function is called before we initialize or use SPI.)
+        */
+       oxygen_clear_bits8(chip, OXYGEN_FUNCTION,
+                          OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+       /*
+        * Read the subsystem device ID directly from the EEPROM, because the
+        * chip didn't if the first EEPROM word was overwritten.
+        */
+       subdevice = oxygen_read_eeprom(chip, 2);
+       /*
+        * We use only the subsystem device ID for searching because it is
+        * unique even without the subsystem vendor ID, which may have been
+        * overwritten in the EEPROM.
+        */
+       for (; ids->vendor; ++ids)
+               if (ids->subdevice == subdevice &&
+                   ids->driver_data != BROKEN_EEPROM_DRIVER_DATA)
+                       return ids;
+       return NULL;
+}
+
 static void oxygen_init(struct oxygen *chip)
 {
        unsigned int i;
@@ -455,11 +483,15 @@ static void oxygen_card_free(struct snd_card *card)
 
 int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
                     struct module *owner,
-                    const struct oxygen_model *model,
-                    unsigned long driver_data)
+                    const struct pci_device_id *ids,
+                    int (*get_model)(struct oxygen *chip,
+                                     const struct pci_device_id *id
+                                    )
+                   )
 {
        struct snd_card *card;
        struct oxygen *chip;
+       const struct pci_device_id *pci_id;
        int err;
 
        err = snd_card_create(index, id, owner, sizeof(*chip), &card);
@@ -470,7 +502,6 @@ int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
        chip->card = card;
        chip->pci = pci;
        chip->irq = -1;
-       chip->model = *model;
        spin_lock_init(&chip->reg_lock);
        mutex_init(&chip->mutex);
        INIT_WORK(&chip->spdif_input_bits_work,
@@ -496,6 +527,15 @@ int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
        }
        chip->addr = pci_resource_start(pci, 0);
 
+       pci_id = oxygen_search_pci_id(chip, ids);
+       if (!pci_id) {
+               err = -ENODEV;
+               goto err_pci_regions;
+       }
+       err = get_model(chip, pci_id);
+       if (err < 0)
+               goto err_pci_regions;
+
        if (chip->model.model_data_size) {
                chip->model_data = kmalloc(chip->model.model_data_size,
                                           GFP_KERNEL);
@@ -509,11 +549,6 @@ int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
        snd_card_set_dev(card, &pci->dev);
        card->private_free = oxygen_card_free;
 
-       if (chip->model.probe) {
-               err = chip->model.probe(chip, driver_data);
-               if (err < 0)
-                       goto err_card;
-       }
        oxygen_init(chip);
        chip->model.init(chip);
 
index c05f7e7..4ac4977 100644 (file)
@@ -160,6 +160,7 @@ static struct pci_device_id xonar_ids[] __devinitdata = {
        { OXYGEN_PCI_SUBID(0x1043, 0x82b7), .driver_data = MODEL_D2X },
        { OXYGEN_PCI_SUBID(0x1043, 0x8314), .driver_data = MODEL_HDAV },
        { OXYGEN_PCI_SUBID(0x1043, 0x834f), .driver_data = MODEL_D1 },
+       { OXYGEN_PCI_SUBID_BROKEN_EEPROM },
        { }
 };
 MODULE_DEVICE_TABLE(pci, xonar_ids);
@@ -188,7 +189,6 @@ MODULE_DEVICE_TABLE(pci, xonar_ids);
 #define I2C_DEVICE_CS4362A     0x30    /* 001100, AD0=0, /W=0 */
 
 struct xonar_data {
-       unsigned int model;
        unsigned int anti_pop_delay;
        unsigned int dacs;
        u16 output_enable_bit;
@@ -334,15 +334,9 @@ static void xonar_d2_init(struct oxygen *chip)
        struct xonar_data *data = chip->model_data;
 
        data->anti_pop_delay = 300;
+       data->dacs = 4;
        data->output_enable_bit = GPIO_D2_OUTPUT_ENABLE;
        data->pcm1796_oversampling = PCM1796_OS_64;
-       if (data->model == MODEL_D2X) {
-               data->ext_power_reg = OXYGEN_GPIO_DATA;
-               data->ext_power_int_reg = OXYGEN_GPIO_INTERRUPT_MASK;
-               data->ext_power_bit = GPIO_D2X_EXT_POWER;
-               oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
-                                   GPIO_D2X_EXT_POWER);
-       }
 
        pcm1796_init(chip);
 
@@ -355,6 +349,18 @@ static void xonar_d2_init(struct oxygen *chip)
        snd_component_add(chip->card, "CS5381");
 }
 
+static void xonar_d2x_init(struct oxygen *chip)
+{
+       struct xonar_data *data = chip->model_data;
+
+       data->ext_power_reg = OXYGEN_GPIO_DATA;
+       data->ext_power_int_reg = OXYGEN_GPIO_INTERRUPT_MASK;
+       data->ext_power_bit = GPIO_D2X_EXT_POWER;
+       oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2X_EXT_POWER);
+
+       xonar_d2_init(chip);
+}
+
 static void update_cs4362a_volumes(struct oxygen *chip)
 {
        u8 mute;
@@ -422,11 +428,6 @@ static void xonar_d1_init(struct oxygen *chip)
        data->cs4398_fm = CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST;
        data->cs4362a_fm = CS4362A_FM_SINGLE |
                CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
-       if (data->model == MODEL_DX) {
-               data->ext_power_reg = OXYGEN_GPI_DATA;
-               data->ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
-               data->ext_power_bit = GPI_DX_EXT_POWER;
-       }
 
        oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
                       OXYGEN_2WIRE_LENGTH_8 |
@@ -447,6 +448,17 @@ static void xonar_d1_init(struct oxygen *chip)
        snd_component_add(chip->card, "CS5361");
 }
 
+static void xonar_dx_init(struct oxygen *chip)
+{
+       struct xonar_data *data = chip->model_data;
+
+       data->ext_power_reg = OXYGEN_GPI_DATA;
+       data->ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+       data->ext_power_bit = GPI_DX_EXT_POWER;
+
+       xonar_d1_init(chip);
+}
+
 static void xonar_hdav_init(struct oxygen *chip)
 {
        struct xonar_data *data = chip->model_data;
@@ -458,6 +470,7 @@ static void xonar_hdav_init(struct oxygen *chip)
                       OXYGEN_2WIRE_SPEED_FAST);
 
        data->anti_pop_delay = 100;
+       data->dacs = chip->model.private_data == MODEL_HDAV_H6 ? 4 : 1;
        data->output_enable_bit = GPIO_DX_OUTPUT_ENABLE;
        data->ext_power_reg = OXYGEN_GPI_DATA;
        data->ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
@@ -773,50 +786,9 @@ static int xonar_d1_mixer_init(struct oxygen *chip)
        return snd_ctl_add(chip->card, snd_ctl_new1(&front_panel_switch, chip));
 }
 
-static int xonar_model_probe(struct oxygen *chip, unsigned long driver_data)
-{
-       static const char *const names[] = {
-               [MODEL_D1]      = "Xonar D1",
-               [MODEL_DX]      = "Xonar DX",
-               [MODEL_D2]      = "Xonar D2",
-               [MODEL_D2X]     = "Xonar D2X",
-               [MODEL_HDAV]    = "Xonar HDAV1.3",
-               [MODEL_HDAV_H6] = "Xonar HDAV1.3+H6",
-       };
-       static const u8 dacs[] = {
-               [MODEL_D1]      = 2,
-               [MODEL_DX]      = 2,
-               [MODEL_D2]      = 4,
-               [MODEL_D2X]     = 4,
-               [MODEL_HDAV]    = 1,
-               [MODEL_HDAV_H6] = 4,
-       };
-       struct xonar_data *data = chip->model_data;
-
-       data->model = driver_data;
-       if (data->model == MODEL_HDAV) {
-               oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
-                                   GPIO_HDAV_DB_MASK);
-               switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) &
-                       GPIO_HDAV_DB_MASK) {
-               case GPIO_HDAV_DB_H6:
-                       data->model = MODEL_HDAV_H6;
-                       break;
-               case GPIO_HDAV_DB_XX:
-                       snd_printk(KERN_ERR "unknown daughterboard\n");
-                       return -ENODEV;
-               }
-       }
-
-       data->dacs = dacs[data->model];
-       chip->model.shortname = names[data->model];
-       return 0;
-}
-
 static const struct oxygen_model model_xonar_d2 = {
        .longname = "Asus Virtuoso 200",
        .chip = "AV200",
-       .probe = xonar_model_probe,
        .init = xonar_d2_init,
        .control_filter = xonar_d2_control_filter,
        .mixer_init = xonar_d2_mixer_init,
@@ -848,7 +820,6 @@ static const struct oxygen_model model_xonar_d2 = {
 static const struct oxygen_model model_xonar_d1 = {
        .longname = "Asus Virtuoso 100",
        .chip = "AV200",
-       .probe = xonar_model_probe,
        .init = xonar_d1_init,
        .control_filter = xonar_d1_control_filter,
        .mixer_init = xonar_d1_mixer_init,
@@ -876,7 +847,6 @@ static const struct oxygen_model model_xonar_d1 = {
 static const struct oxygen_model model_xonar_hdav = {
        .longname = "Asus Virtuoso 200",
        .chip = "AV200",
-       .probe = xonar_model_probe,
        .init = xonar_hdav_init,
        .cleanup = xonar_hdav_cleanup,
        .suspend = xonar_hdav_suspend,
@@ -902,8 +872,8 @@ static const struct oxygen_model model_xonar_hdav = {
        .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
 };
 
-static int __devinit xonar_probe(struct pci_dev *pci,
-                                const struct pci_device_id *pci_id)
+static int __devinit get_xonar_model(struct oxygen *chip,
+                                    const struct pci_device_id *id)
 {
        static const struct oxygen_model *const models[] = {
                [MODEL_D1]      = &model_xonar_d1,
@@ -912,6 +882,50 @@ static int __devinit xonar_probe(struct pci_dev *pci,
                [MODEL_D2X]     = &model_xonar_d2,
                [MODEL_HDAV]    = &model_xonar_hdav,
        };
+       static const char *const names[] = {
+               [MODEL_D1]      = "Xonar D1",
+               [MODEL_DX]      = "Xonar DX",
+               [MODEL_D2]      = "Xonar D2",
+               [MODEL_D2X]     = "Xonar D2X",
+               [MODEL_HDAV]    = "Xonar HDAV1.3",
+               [MODEL_HDAV_H6] = "Xonar HDAV1.3+H6",
+       };
+       unsigned int model = id->driver_data;
+
+       if (model >= ARRAY_SIZE(models) || !models[model])
+               return -EINVAL;
+       chip->model = *models[model];
+
+       switch (model) {
+       case MODEL_D2X:
+               chip->model.init = xonar_d2x_init;
+               break;
+       case MODEL_DX:
+               chip->model.init = xonar_dx_init;
+               break;
+       case MODEL_HDAV:
+               oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
+                                   GPIO_HDAV_DB_MASK);
+               switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) &
+                       GPIO_HDAV_DB_MASK) {
+               case GPIO_HDAV_DB_H6:
+                       model = MODEL_HDAV_H6;
+                       break;
+               case GPIO_HDAV_DB_XX:
+                       snd_printk(KERN_ERR "unknown daughterboard\n");
+                       return -ENODEV;
+               }
+               break;
+       }
+
+       chip->model.shortname = names[model];
+       chip->model.private_data = model;
+       return 0;
+}
+
+static int __devinit xonar_probe(struct pci_dev *pci,
+                                const struct pci_device_id *pci_id)
+{
        static int dev;
        int err;
 
@@ -921,10 +935,8 @@ static int __devinit xonar_probe(struct pci_dev *pci,
                ++dev;
                return -ENOENT;
        }
-       BUG_ON(pci_id->driver_data >= ARRAY_SIZE(models));
        err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
-                              models[pci_id->driver_data],
-                              pci_id->driver_data);
+                              xonar_ids, get_xonar_model);
        if (err >= 0)
                ++dev;
        return err;