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 cc98bad9916afe82a75a6a90f58b73132f323c0d..84ef131834191b9b64c6d2b1ed266a39573ea7d0 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 12b6c2137d5021df82a6d89d9348bc36b652cafd..f2c37f379d39e64b9d16704082f6093a117bbe11 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 268bff4f29d23073d96b29a5f916ae3f0ca336d5..c500d48ea349915ded4fc786b09171fcdcd00054 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 3126c4b403dd2634e2f29c0d1c5ca68a566c2370..05f48ef1a442683839abf75148adaf71398c97cd 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 516d94ad2bbba0e93285270c8d064d57603da609..d83c3a9573233dc9c5879832f9e40eab5db4e288 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 c05f7e7bdb34dfdd147c99f51ed4e7292f77650c..4ac49772da8cfd4bacc4a81bf87fc84cef0a1f65 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;
This page took 0.032824 seconds and 5 git commands to generate.