Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30459d7b authored by Clemens Ladisch's avatar Clemens Ladisch Committed by Takashi Iwai
Browse files

sound: oxygen: handle cards with broken EEPROM



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: default avatarClemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent a69bb3c3
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -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;
+24 −20
Original line number Diff line number Diff line
@@ -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;
+14 −3
Original line number Diff line number Diff line
@@ -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)
{
+15 −0
Original line number Diff line number Diff line
@@ -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);
}
+43 −8
Original line number Diff line number Diff line
@@ -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);

Loading