Skip to content

Commit

Permalink
sound: oxygen: handle cards with broken EEPROM
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Clemens Ladisch authored and Takashi Iwai committed Feb 19, 2009
1 parent a69bb3c commit 30459d7
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 93 deletions.
11 changes: 10 additions & 1 deletion sound/pci/oxygen/hifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down
44 changes: 24 additions & 20 deletions sound/pci/oxygen/oxygen.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
{
Expand All @@ -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;
Expand Down
17 changes: 14 additions & 3 deletions sound/pci/oxygen/oxygen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down
15 changes: 15 additions & 0 deletions sound/pci/oxygen/oxygen_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
51 changes: 43 additions & 8 deletions sound/pci/oxygen/oxygen_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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);

Expand Down
Loading

0 comments on commit 30459d7

Please sign in to comment.