diff options
Diffstat (limited to 'drivers/net/wireless/orinoco/hermes.c')
| -rw-r--r-- | drivers/net/wireless/orinoco/hermes.c | 208 | 
1 files changed, 208 insertions, 0 deletions
diff --git a/drivers/net/wireless/orinoco/hermes.c b/drivers/net/wireless/orinoco/hermes.c index 845693fb25f..6c6a23e08df 100644 --- a/drivers/net/wireless/orinoco/hermes.c +++ b/drivers/net/wireless/orinoco/hermes.c @@ -52,6 +52,26 @@  #define ALLOC_COMPL_TIMEOUT (1000) /* in iterations of ~10us */  /* + * AUX port access.  To unlock the AUX port write the access keys to the + * PARAM0-2 registers, then write HERMES_AUX_ENABLE to the HERMES_CONTROL + * register.  Then read it and make sure it's HERMES_AUX_ENABLED. + */ +#define HERMES_AUX_ENABLE	0x8000	/* Enable auxiliary port access */ +#define HERMES_AUX_DISABLE	0x4000	/* Disable to auxiliary port access */ +#define HERMES_AUX_ENABLED	0xC000	/* Auxiliary port is open */ +#define HERMES_AUX_DISABLED	0x0000	/* Auxiliary port is closed */ + +#define HERMES_AUX_PW0	0xFE01 +#define HERMES_AUX_PW1	0xDC23 +#define HERMES_AUX_PW2	0xBA45 + +/* HERMES_CMD_DOWNLD */ +#define HERMES_PROGRAM_DISABLE             (0x0000 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_VOLATILE     (0x0100 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_NON_VOLATILE (0x0200 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_NON_VOLATILE        (0x0300 | HERMES_CMD_DOWNLD) + +/*   * Debugging helpers   */ @@ -170,6 +190,7 @@ void hermes_struct_init(hermes_t *hw, void __iomem *address, int reg_spacing)  	hw->iobase = address;  	hw->reg_spacing = reg_spacing;  	hw->inten = 0x0; +	hw->eeprom_pda = false;  	hw->ops = &hermes_ops_local;  }  EXPORT_SYMBOL(hermes_struct_init); @@ -529,6 +550,189 @@ static int hermes_write_ltv(hermes_t *hw, int bap, u16 rid,  	return err;  } +/*** Hermes AUX control ***/ + +static inline void +hermes_aux_setaddr(hermes_t *hw, u32 addr) +{ +	hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7)); +	hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F)); +} + +static inline int +hermes_aux_control(hermes_t *hw, int enabled) +{ +	int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED; +	int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE; +	int i; + +	/* Already open? */ +	if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state) +		return 0; + +	hermes_write_reg(hw, HERMES_PARAM0, HERMES_AUX_PW0); +	hermes_write_reg(hw, HERMES_PARAM1, HERMES_AUX_PW1); +	hermes_write_reg(hw, HERMES_PARAM2, HERMES_AUX_PW2); +	hermes_write_reg(hw, HERMES_CONTROL, action); + +	for (i = 0; i < 20; i++) { +		udelay(10); +		if (hermes_read_reg(hw, HERMES_CONTROL) == +		    desired_state) +			return 0; +	} + +	return -EBUSY; +} + +/*** Hermes programming ***/ + +/* About to start programming data (Hermes I) + * offset is the entry point + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_init(hermes_t *hw, u32 offset) +{ +	int err; + +	/* Disable interrupts?*/ +	/*hw->inten = 0x0;*/ +	/*hermes_write_regn(hw, INTEN, 0);*/ +	/*hermes_set_irqmask(hw, 0);*/ + +	/* Acknowledge any outstanding command */ +	hermes_write_regn(hw, EVACK, 0xFFFF); + +	/* Using init_cmd_wait rather than cmd_wait */ +	err = hw->ops->init_cmd_wait(hw, +				     0x0100 | HERMES_CMD_INIT, +				     0, 0, 0, NULL); +	if (err) +		return err; + +	err = hw->ops->init_cmd_wait(hw, +				     0x0000 | HERMES_CMD_INIT, +				     0, 0, 0, NULL); +	if (err) +		return err; + +	err = hermes_aux_control(hw, 1); +	pr_debug("AUX enable returned %d\n", err); + +	if (err) +		return err; + +	pr_debug("Enabling volatile, EP 0x%08x\n", offset); +	err = hw->ops->init_cmd_wait(hw, +				     HERMES_PROGRAM_ENABLE_VOLATILE, +				     offset & 0xFFFFu, +				     offset >> 16, +				     0, +				     NULL); +	pr_debug("PROGRAM_ENABLE returned %d\n", err); + +	return err; +} + +/* Done programming data (Hermes I) + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_end(hermes_t *hw) +{ +	struct hermes_response resp; +	int rc = 0; +	int err; + +	rc = hw->ops->cmd_wait(hw, HERMES_PROGRAM_DISABLE, 0, &resp); + +	pr_debug("PROGRAM_DISABLE returned %d, " +		 "r0 0x%04x, r1 0x%04x, r2 0x%04x\n", +		 rc, resp.resp0, resp.resp1, resp.resp2); + +	if ((rc == 0) && +	    ((resp.status & HERMES_STATUS_CMDCODE) != HERMES_CMD_DOWNLD)) +		rc = -EIO; + +	err = hermes_aux_control(hw, 0); +	pr_debug("AUX disable returned %d\n", err); + +	/* Acknowledge any outstanding command */ +	hermes_write_regn(hw, EVACK, 0xFFFF); + +	/* Reinitialise, ignoring return */ +	(void) hw->ops->init_cmd_wait(hw, 0x0000 | HERMES_CMD_INIT, +				      0, 0, 0, NULL); + +	return rc ? rc : err; +} + +static int hermes_program_bytes(struct hermes *hw, const char *data, +				u32 addr, u32 len) +{ +	/* wl lkm splits the programming into chunks of 2000 bytes. +	 * This restriction appears to come from USB. The PCMCIA +	 * adapters can program the whole lot in one go */ +	hermes_aux_setaddr(hw, addr); +	hermes_write_bytes(hw, HERMES_AUXDATA, data, len); +	return 0; +} + +/* Read PDA from the adapter */ +static int hermes_read_pda(hermes_t *hw, __le16 *pda, u32 pda_addr, u16 pda_len) +{ +	int ret; +	u16 pda_size; +	u16 data_len = pda_len; +	__le16 *data = pda; + +	if (hw->eeprom_pda) { +		/* PDA of spectrum symbol is in eeprom */ + +		/* Issue command to read EEPROM */ +		ret = hw->ops->cmd_wait(hw, HERMES_CMD_READMIF, 0, NULL); +		if (ret) +			return ret; +	} else { +		/* wl_lkm does not include PDA size in the PDA area. +		 * We will pad the information into pda, so other routines +		 * don't have to be modified */ +		pda[0] = cpu_to_le16(pda_len - 2); +			/* Includes CFG_PROD_DATA but not itself */ +		pda[1] = cpu_to_le16(0x0800); /* CFG_PROD_DATA */ +		data_len = pda_len - 4; +		data = pda + 2; +	} + +	/* Open auxiliary port */ +	ret = hermes_aux_control(hw, 1); +	pr_debug("AUX enable returned %d\n", ret); +	if (ret) +		return ret; + +	/* Read PDA */ +	hermes_aux_setaddr(hw, pda_addr); +	hermes_read_words(hw, HERMES_AUXDATA, data, data_len / 2); + +	/* Close aux port */ +	ret = hermes_aux_control(hw, 0); +	pr_debug("AUX disable returned %d\n", ret); + +	/* Check PDA length */ +	pda_size = le16_to_cpu(pda[0]); +	pr_debug("Actual PDA length %d, Max allowed %d\n", +		 pda_size, pda_len); +	if (pda_size > pda_len) +		return -EINVAL; + +	return 0; +} +  static void hermes_lock_irqsave(spinlock_t *lock,  				unsigned long *flags) __acquires(lock)  { @@ -561,6 +765,10 @@ static const struct hermes_ops hermes_ops_local = {  	.write_ltv = hermes_write_ltv,  	.bap_pread = hermes_bap_pread,  	.bap_pwrite = hermes_bap_pwrite, +	.read_pda = hermes_read_pda, +	.program_init = hermesi_program_init, +	.program_end = hermesi_program_end, +	.program = hermes_program_bytes,  	.lock_irqsave = hermes_lock_irqsave,  	.unlock_irqrestore = hermes_unlock_irqrestore,  	.lock_irq = hermes_lock_irq,  |