summary refs log tree commit diff
path: root/drivers/net/wireless/ath5k/eeprom.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ath5k/eeprom.c')
-rw-r--r--drivers/net/wireless/ath5k/eeprom.c774
1 files changed, 542 insertions, 232 deletions
diff --git a/drivers/net/wireless/ath5k/eeprom.c b/drivers/net/wireless/ath5k/eeprom.c
index ac45ca47ca87..c0fb3b09ba45 100644
--- a/drivers/net/wireless/ath5k/eeprom.c
+++ b/drivers/net/wireless/ath5k/eeprom.c
@@ -1,7 +1,7 @@
 /*
  * Copyright (c) 2004-2008 Reyk Floeter <reyk@openbsd.org>
- * Copyright (c) 2006-2008 Nick Kossifidis <mickflemm@gmail.com>
- * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com>
+ * Copyright (c) 2008-2009 Felix Fietkau <nbd@openwrt.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -98,11 +98,6 @@ ath5k_eeprom_init_header(struct ath5k_hw *ah)
 	int ret;
 	u16 val;
 
-	/* Initial TX thermal adjustment values */
-	ee->ee_tx_clip = 4;
-	ee->ee_pwd_84 = ee->ee_pwd_90 = 1;
-	ee->ee_gain_select = 1;
-
 	/*
 	 * Read values from EEPROM and store them in the capability structure
 	 */
@@ -241,22 +236,22 @@ static int ath5k_eeprom_read_modes(struct ath5k_hw *ah, u32 *offset,
 	ee->ee_adc_desired_size[mode]	= (s8)((val >> 8) & 0xff);
 	switch(mode) {
 	case AR5K_EEPROM_MODE_11A:
-		ee->ee_ob[mode][3]		= (val >> 5) & 0x7;
-		ee->ee_db[mode][3]		= (val >> 2) & 0x7;
-		ee->ee_ob[mode][2]		= (val << 1) & 0x7;
+		ee->ee_ob[mode][3]	= (val >> 5) & 0x7;
+		ee->ee_db[mode][3]	= (val >> 2) & 0x7;
+		ee->ee_ob[mode][2]	= (val << 1) & 0x7;
 
 		AR5K_EEPROM_READ(o++, val);
-		ee->ee_ob[mode][2]		|= (val >> 15) & 0x1;
-		ee->ee_db[mode][2]		= (val >> 12) & 0x7;
-		ee->ee_ob[mode][1]		= (val >> 9) & 0x7;
-		ee->ee_db[mode][1]		= (val >> 6) & 0x7;
-		ee->ee_ob[mode][0]		= (val >> 3) & 0x7;
-		ee->ee_db[mode][0]		= val & 0x7;
+		ee->ee_ob[mode][2]	|= (val >> 15) & 0x1;
+		ee->ee_db[mode][2]	= (val >> 12) & 0x7;
+		ee->ee_ob[mode][1]	= (val >> 9) & 0x7;
+		ee->ee_db[mode][1]	= (val >> 6) & 0x7;
+		ee->ee_ob[mode][0]	= (val >> 3) & 0x7;
+		ee->ee_db[mode][0]	= val & 0x7;
 		break;
 	case AR5K_EEPROM_MODE_11G:
 	case AR5K_EEPROM_MODE_11B:
-		ee->ee_ob[mode][1]		= (val >> 4) & 0x7;
-		ee->ee_db[mode][1]		= val & 0x7;
+		ee->ee_ob[mode][1]	= (val >> 4) & 0x7;
+		ee->ee_db[mode][1]	= val & 0x7;
 		break;
 	}
 
@@ -504,35 +499,6 @@ ath5k_eeprom_init_modes(struct ath5k_hw *ah)
 	return 0;
 }
 
-/* Used to match PCDAC steps with power values on RF5111 chips
- * (eeprom versions < 4). For RF5111 we have 10 pre-defined PCDAC
- * steps that match with the power values we read from eeprom. On
- * older eeprom versions (< 3.2) these steps are equaly spaced at
- * 10% of the pcdac curve -until the curve reaches it's maximum-
- * (10 steps from 0 to 100%) but on newer eeprom versions (>= 3.2)
- * these 10 steps are spaced in a different way. This function returns
- * the pcdac steps based on eeprom version and curve min/max so that we
- * can have  pcdac/pwr points.
- */
-static inline void
-ath5k_get_pcdac_intercepts(struct ath5k_hw *ah, u8 min, u8 max, u8 *vp)
-{
-	static const u16 intercepts3[] =
-		{ 0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100 };
-	static const u16 intercepts3_2[] =
-		{ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
-	const u16 *ip;
-	int i;
-
-	if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_2)
-		ip = intercepts3_2;
-	else
-		ip = intercepts3;
-
-	for (i = 0; i < ARRAY_SIZE(intercepts3); i++)
-		*vp++ = (ip[i] * max + (100 - ip[i]) * min) / 100;
-}
-
 /* Read the frequency piers for each mode (mostly used on newer eeproms with 0xff
  * frequency mask) */
 static inline int
@@ -546,26 +512,25 @@ ath5k_eeprom_read_freq_list(struct ath5k_hw *ah, int *offset, int max,
 	int ret;
 	u16 val;
 
+	ee->ee_n_piers[mode] = 0;
 	while(i < max) {
 		AR5K_EEPROM_READ(o++, val);
 
-		freq1 = (val >> 8) & 0xff;
-		freq2 = val & 0xff;
-
-		if (freq1) {
-			pc[i++].freq = ath5k_eeprom_bin2freq(ee,
-					freq1, mode);
-			ee->ee_n_piers[mode]++;
-		}
+		freq1 = val & 0xff;
+		if (!freq1)
+			break;
 
-		if (freq2) {
-			pc[i++].freq = ath5k_eeprom_bin2freq(ee,
-					freq2, mode);
-			ee->ee_n_piers[mode]++;
-		}
+		pc[i++].freq = ath5k_eeprom_bin2freq(ee,
+				freq1, mode);
+		ee->ee_n_piers[mode]++;
 
-		if (!freq1 || !freq2)
+		freq2 = (val >> 8) & 0xff;
+		if (!freq2)
 			break;
+
+		pc[i++].freq = ath5k_eeprom_bin2freq(ee,
+				freq2, mode);
+		ee->ee_n_piers[mode]++;
 	}
 
 	/* return new offset */
@@ -652,13 +617,122 @@ ath5k_eeprom_init_11bg_2413(struct ath5k_hw *ah, unsigned int mode, int offset)
 	return 0;
 }
 
-/* Read power calibration for RF5111 chips
+/*
+ * Read power calibration for RF5111 chips
+ *
  * For RF5111 we have an XPD -eXternal Power Detector- curve
- * for each calibrated channel. Each curve has PCDAC steps on
- * x axis and power on y axis and looks like a logarithmic
- * function. To recreate the curve and pass the power values
- * on the pcdac table, we read 10 points here and interpolate later.
+ * for each calibrated channel. Each curve has 0,5dB Power steps
+ * on x axis and PCDAC steps (offsets) on y axis and looks like an
+ * exponential function. To recreate the curve we read 11 points
+ * here and interpolate later.
  */
+
+/* Used to match PCDAC steps with power values on RF5111 chips
+ * (eeprom versions < 4). For RF5111 we have 11 pre-defined PCDAC
+ * steps that match with the power values we read from eeprom. On
+ * older eeprom versions (< 3.2) these steps are equaly spaced at
+ * 10% of the pcdac curve -until the curve reaches it's maximum-
+ * (11 steps from 0 to 100%) but on newer eeprom versions (>= 3.2)
+ * these 11 steps are spaced in a different way. This function returns
+ * the pcdac steps based on eeprom version and curve min/max so that we
+ * can have pcdac/pwr points.
+ */
+static inline void
+ath5k_get_pcdac_intercepts(struct ath5k_hw *ah, u8 min, u8 max, u8 *vp)
+{
+	const static u16 intercepts3[] =
+		{ 0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100 };
+	const static u16 intercepts3_2[] =
+		{ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
+	const u16 *ip;
+	int i;
+
+	if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_2)
+		ip = intercepts3_2;
+	else
+		ip = intercepts3;
+
+	for (i = 0; i < ARRAY_SIZE(intercepts3); i++)
+		vp[i] = (ip[i] * max + (100 - ip[i]) * min) / 100;
+}
+
+/* Convert RF5111 specific data to generic raw data
+ * used by interpolation code */
+static int
+ath5k_eeprom_convert_pcal_info_5111(struct ath5k_hw *ah, int mode,
+				struct ath5k_chan_pcal_info *chinfo)
+{
+	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+	struct ath5k_chan_pcal_info_rf5111 *pcinfo;
+	struct ath5k_pdgain_info *pd;
+	u8 pier, point, idx;
+	u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
+
+	/* Fill raw data for each calibration pier */
+	for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
+
+		pcinfo = &chinfo[pier].rf5111_info;
+
+		/* Allocate pd_curves for this cal pier */
+		chinfo[pier].pd_curves =
+			kcalloc(AR5K_EEPROM_N_PD_CURVES,
+				sizeof(struct ath5k_pdgain_info),
+				GFP_KERNEL);
+
+		if (!chinfo[pier].pd_curves)
+			return -ENOMEM;
+
+		/* Only one curve for RF5111
+		 * find out which one and place
+		 * in in pd_curves.
+		 * Note: ee_x_gain is reversed here */
+		for (idx = 0; idx < AR5K_EEPROM_N_PD_CURVES; idx++) {
+
+			if (!((ee->ee_x_gain[mode] >> idx) & 0x1)) {
+				pdgain_idx[0] = idx;
+				break;
+			}
+		}
+
+		ee->ee_pd_gains[mode] = 1;
+
+		pd = &chinfo[pier].pd_curves[idx];
+
+		pd->pd_points = AR5K_EEPROM_N_PWR_POINTS_5111;
+
+		/* Allocate pd points for this curve */
+		pd->pd_step = kcalloc(AR5K_EEPROM_N_PWR_POINTS_5111,
+					sizeof(u8), GFP_KERNEL);
+		if (!pd->pd_step)
+			return -ENOMEM;
+
+		pd->pd_pwr = kcalloc(AR5K_EEPROM_N_PWR_POINTS_5111,
+					sizeof(s16), GFP_KERNEL);
+		if (!pd->pd_pwr)
+			return -ENOMEM;
+
+		/* Fill raw dataset
+		 * (convert power to 0.25dB units
+		 * for RF5112 combatibility) */
+		for (point = 0; point < pd->pd_points; point++) {
+
+			/* Absolute values */
+			pd->pd_pwr[point] = 2 * pcinfo->pwr[point];
+
+			/* Already sorted */
+			pd->pd_step[point] = pcinfo->pcdac[point];
+		}
+
+		/* Set min/max pwr */
+		chinfo[pier].min_pwr = pd->pd_pwr[0];
+		chinfo[pier].max_pwr = pd->pd_pwr[10];
+
+	}
+
+	return 0;
+}
+
+/* Parse EEPROM data */
 static int
 ath5k_eeprom_read_pcal_info_5111(struct ath5k_hw *ah, int mode)
 {
@@ -747,30 +821,165 @@ ath5k_eeprom_read_pcal_info_5111(struct ath5k_hw *ah, int mode)
 			cdata->pcdac_max, cdata->pcdac);
 	}
 
-	return 0;
+	return ath5k_eeprom_convert_pcal_info_5111(ah, mode, pcal);
 }
 
-/* Read power calibration for RF5112 chips
+
+/*
+ * Read power calibration for RF5112 chips
+ *
  * For RF5112 we have 4 XPD -eXternal Power Detector- curves
  * for each calibrated channel on 0, -6, -12 and -18dbm but we only
- * use the higher (3) and the lower (0) curves. Each curve has PCDAC
- * steps on x axis and power on y axis and looks like a linear
- * function. To recreate the curve and pass the power values
- * on the pcdac table, we read 4 points for xpd 0 and 3 points
- * for xpd 3 here and interpolate later.
+ * use the higher (3) and the lower (0) curves. Each curve has 0.5dB
+ * power steps on x axis and PCDAC steps on y axis and looks like a
+ * linear function. To recreate the curve and pass the power values
+ * on hw, we read 4 points for xpd 0 (lower gain -> max power)
+ * and 3 points for xpd 3 (higher gain -> lower power) here and
+ * interpolate later.
  *
  * Note: Many vendors just use xpd 0 so xpd 3 is zeroed.
  */
+
+/* Convert RF5112 specific data to generic raw data
+ * used by interpolation code */
+static int
+ath5k_eeprom_convert_pcal_info_5112(struct ath5k_hw *ah, int mode,
+				struct ath5k_chan_pcal_info *chinfo)
+{
+	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+	struct ath5k_chan_pcal_info_rf5112 *pcinfo;
+	u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
+	unsigned int pier, pdg, point;
+
+	/* Fill raw data for each calibration pier */
+	for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
+
+		pcinfo = &chinfo[pier].rf5112_info;
+
+		/* Allocate pd_curves for this cal pier */
+		chinfo[pier].pd_curves =
+				kcalloc(AR5K_EEPROM_N_PD_CURVES,
+					sizeof(struct ath5k_pdgain_info),
+					GFP_KERNEL);
+
+		if (!chinfo[pier].pd_curves)
+			return -ENOMEM;
+
+		/* Fill pd_curves */
+		for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) {
+
+			u8 idx = pdgain_idx[pdg];
+			struct ath5k_pdgain_info *pd =
+					&chinfo[pier].pd_curves[idx];
+
+			/* Lowest gain curve (max power) */
+			if (pdg == 0) {
+				/* One more point for better accuracy */
+				pd->pd_points = AR5K_EEPROM_N_XPD0_POINTS;
+
+				/* Allocate pd points for this curve */
+				pd->pd_step = kcalloc(pd->pd_points,
+						sizeof(u8), GFP_KERNEL);
+
+				if (!pd->pd_step)
+					return -ENOMEM;
+
+				pd->pd_pwr = kcalloc(pd->pd_points,
+						sizeof(s16), GFP_KERNEL);
+
+				if (!pd->pd_pwr)
+					return -ENOMEM;
+
+
+				/* Fill raw dataset
+				 * (all power levels are in 0.25dB units) */
+				pd->pd_step[0] = pcinfo->pcdac_x0[0];
+				pd->pd_pwr[0] = pcinfo->pwr_x0[0];
+
+				for (point = 1; point < pd->pd_points;
+				point++) {
+					/* Absolute values */
+					pd->pd_pwr[point] =
+						pcinfo->pwr_x0[point];
+
+					/* Deltas */
+					pd->pd_step[point] =
+						pd->pd_step[point - 1] +
+						pcinfo->pcdac_x0[point];
+				}
+
+				/* Set min power for this frequency */
+				chinfo[pier].min_pwr = pd->pd_pwr[0];
+
+			/* Highest gain curve (min power) */
+			} else if (pdg == 1) {
+
+				pd->pd_points = AR5K_EEPROM_N_XPD3_POINTS;
+
+				/* Allocate pd points for this curve */
+				pd->pd_step = kcalloc(pd->pd_points,
+						sizeof(u8), GFP_KERNEL);
+
+				if (!pd->pd_step)
+					return -ENOMEM;
+
+				pd->pd_pwr = kcalloc(pd->pd_points,
+						sizeof(s16), GFP_KERNEL);
+
+				if (!pd->pd_pwr)
+					return -ENOMEM;
+
+				/* Fill raw dataset
+				 * (all power levels are in 0.25dB units) */
+				for (point = 0; point < pd->pd_points;
+				point++) {
+					/* Absolute values */
+					pd->pd_pwr[point] =
+						pcinfo->pwr_x3[point];
+
+					/* Fixed points */
+					pd->pd_step[point] =
+						pcinfo->pcdac_x3[point];
+				}
+
+				/* Since we have a higher gain curve
+				 * override min power */
+				chinfo[pier].min_pwr = pd->pd_pwr[0];
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* Parse EEPROM data */
 static int
 ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode)
 {
 	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
 	struct ath5k_chan_pcal_info_rf5112 *chan_pcal_info;
 	struct ath5k_chan_pcal_info *gen_chan_info;
+	u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
 	u32 offset;
-	unsigned int i, c;
+	u8 i, c;
 	u16 val;
 	int ret;
+	u8 pd_gains = 0;
+
+	/* Count how many curves we have and
+	 * identify them (which one of the 4
+	 * available curves we have on each count).
+	 * Curves are stored from lower (x0) to
+	 * higher (x3) gain */
+	for (i = 0; i < AR5K_EEPROM_N_PD_CURVES; i++) {
+		/* ee_x_gain[mode] is x gain mask */
+		if ((ee->ee_x_gain[mode] >> i) & 0x1)
+			pdgain_idx[pd_gains++] = i;
+	}
+	ee->ee_pd_gains[mode] = pd_gains;
+
+	if (pd_gains == 0 || pd_gains > 2)
+		return -EINVAL;
 
 	switch (mode) {
 	case AR5K_EEPROM_MODE_11A:
@@ -808,13 +1017,13 @@ ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode)
 	for (i = 0; i < ee->ee_n_piers[mode]; i++) {
 		chan_pcal_info = &gen_chan_info[i].rf5112_info;
 
-		/* Power values in dBm * 4
+		/* Power values in quarter dB
 		 * for the lower xpd gain curve
 		 * (0 dBm -> higher output power) */
 		for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) {
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr_x0[c] = (val & 0xff);
-			chan_pcal_info->pwr_x0[++c] = ((val >> 8) & 0xff);
+			chan_pcal_info->pwr_x0[c] = (s8) (val & 0xff);
+			chan_pcal_info->pwr_x0[++c] = (s8) ((val >> 8) & 0xff);
 		}
 
 		/* PCDAC steps
@@ -825,12 +1034,12 @@ ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode)
 		chan_pcal_info->pcdac_x0[2] = ((val >> 5) & 0x1f);
 		chan_pcal_info->pcdac_x0[3] = ((val >> 10) & 0x1f);
 
-		/* Power values in dBm * 4
+		/* Power values in quarter dB
 		 * for the higher xpd gain curve
 		 * (18 dBm -> lower output power) */
 		AR5K_EEPROM_READ(offset++, val);
-		chan_pcal_info->pwr_x3[0] = (val & 0xff);
-		chan_pcal_info->pwr_x3[1] = ((val >> 8) & 0xff);
+		chan_pcal_info->pwr_x3[0] = (s8) (val & 0xff);
+		chan_pcal_info->pwr_x3[1] = (s8) ((val >> 8) & 0xff);
 
 		AR5K_EEPROM_READ(offset++, val);
 		chan_pcal_info->pwr_x3[2] = (val & 0xff);
@@ -843,24 +1052,36 @@ ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode)
 		chan_pcal_info->pcdac_x3[2] = 63;
 
 		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) {
-			chan_pcal_info->pcdac_x0[0] = ((val >> 8) & 0xff);
+			chan_pcal_info->pcdac_x0[0] = ((val >> 8) & 0x3f);
 
 			/* Last xpd0 power level is also channel maximum */
 			gen_chan_info[i].max_pwr = chan_pcal_info->pwr_x0[3];
 		} else {
 			chan_pcal_info->pcdac_x0[0] = 1;
-			gen_chan_info[i].max_pwr = ((val >> 8) & 0xff);
+			gen_chan_info[i].max_pwr = (s8) ((val >> 8) & 0xff);
 		}
 
-		/* Recreate pcdac_x0 table for this channel using pcdac steps */
-		chan_pcal_info->pcdac_x0[1] += chan_pcal_info->pcdac_x0[0];
-		chan_pcal_info->pcdac_x0[2] += chan_pcal_info->pcdac_x0[1];
-		chan_pcal_info->pcdac_x0[3] += chan_pcal_info->pcdac_x0[2];
 	}
 
-	return 0;
+	return ath5k_eeprom_convert_pcal_info_5112(ah, mode, gen_chan_info);
 }
 
+
+/*
+ * Read power calibration for RF2413 chips
+ *
+ * For RF2413 we have a Power to PDDAC table (Power Detector)
+ * instead of a PCDAC and 4 pd gain curves for each calibrated channel.
+ * Each curve has power on x axis in 0.5 db steps and PDDADC steps on y
+ * axis and looks like an exponential function like the RF5111 curve.
+ *
+ * To recreate the curves we read here the points and interpolate
+ * later. Note that in most cases only 2 (higher and lower) curves are
+ * used (like RF5112) but vendors have the oportunity to include all
+ * 4 curves on eeprom. The final curve (higher power) has an extra
+ * point for better accuracy like RF5112.
+ */
+
 /* For RF2413 power calibration data doesn't start on a fixed location and
  * if a mode is not supported, it's section is missing -not zeroed-.
  * So we need to calculate the starting offset for each section by using
@@ -890,13 +1111,15 @@ ath5k_cal_data_offset_2413(struct ath5k_eeprom_info *ee, int mode)
 	switch(mode) {
 	case AR5K_EEPROM_MODE_11G:
 		if (AR5K_EEPROM_HDR_11B(ee->ee_header))
-			offset += ath5k_pdgains_size_2413(ee, AR5K_EEPROM_MODE_11B) +
-							AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
+			offset += ath5k_pdgains_size_2413(ee,
+					AR5K_EEPROM_MODE_11B) +
+					AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
 		/* fall through */
 	case AR5K_EEPROM_MODE_11B:
 		if (AR5K_EEPROM_HDR_11A(ee->ee_header))
-			offset += ath5k_pdgains_size_2413(ee, AR5K_EEPROM_MODE_11A) +
-							AR5K_EEPROM_N_5GHZ_CHAN / 2;
+			offset += ath5k_pdgains_size_2413(ee,
+					AR5K_EEPROM_MODE_11A) +
+					AR5K_EEPROM_N_5GHZ_CHAN / 2;
 		/* fall through */
 	case AR5K_EEPROM_MODE_11A:
 		break;
@@ -907,37 +1130,118 @@ ath5k_cal_data_offset_2413(struct ath5k_eeprom_info *ee, int mode)
 	return offset;
 }
 
-/* Read power calibration for RF2413 chips
- * For RF2413 we have a PDDAC table (Power Detector) instead
- * of a PCDAC and 4 pd gain curves for each calibrated channel.
- * Each curve has PDDAC steps on x axis and power on y axis and
- * looks like an exponential function. To recreate the curves
- * we read here the points and interpolate later. Note that
- * in most cases only higher and lower curves are used (like
- * RF5112) but vendors have the oportunity to include all 4
- * curves on eeprom. The final curve (higher power) has an extra
- * point for better accuracy like RF5112.
- */
+/* Convert RF2413 specific data to generic raw data
+ * used by interpolation code */
+static int
+ath5k_eeprom_convert_pcal_info_2413(struct ath5k_hw *ah, int mode,
+				struct ath5k_chan_pcal_info *chinfo)
+{
+	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+	struct ath5k_chan_pcal_info_rf2413 *pcinfo;
+	u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
+	unsigned int pier, pdg, point;
+
+	/* Fill raw data for each calibration pier */
+	for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
+
+		pcinfo = &chinfo[pier].rf2413_info;
+
+		/* Allocate pd_curves for this cal pier */
+		chinfo[pier].pd_curves =
+				kcalloc(AR5K_EEPROM_N_PD_CURVES,
+					sizeof(struct ath5k_pdgain_info),
+					GFP_KERNEL);
+
+		if (!chinfo[pier].pd_curves)
+			return -ENOMEM;
+
+		/* Fill pd_curves */
+		for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) {
+
+			u8 idx = pdgain_idx[pdg];
+			struct ath5k_pdgain_info *pd =
+					&chinfo[pier].pd_curves[idx];
+
+			/* One more point for the highest power
+			 * curve (lowest gain) */
+			if (pdg == ee->ee_pd_gains[mode] - 1)
+				pd->pd_points = AR5K_EEPROM_N_PD_POINTS;
+			else
+				pd->pd_points = AR5K_EEPROM_N_PD_POINTS - 1;
+
+			/* Allocate pd points for this curve */
+			pd->pd_step = kcalloc(pd->pd_points,
+					sizeof(u8), GFP_KERNEL);
+
+			if (!pd->pd_step)
+				return -ENOMEM;
+
+			pd->pd_pwr = kcalloc(pd->pd_points,
+					sizeof(s16), GFP_KERNEL);
+
+			if (!pd->pd_pwr)
+				return -ENOMEM;
+
+			/* Fill raw dataset
+			 * convert all pwr levels to
+			 * quarter dB for RF5112 combatibility */
+			pd->pd_step[0] = pcinfo->pddac_i[pdg];
+			pd->pd_pwr[0] = 4 * pcinfo->pwr_i[pdg];
+
+			for (point = 1; point < pd->pd_points; point++) {
+
+				pd->pd_pwr[point] = pd->pd_pwr[point - 1] +
+					2 * pcinfo->pwr[pdg][point - 1];
+
+				pd->pd_step[point] = pd->pd_step[point - 1] +
+						pcinfo->pddac[pdg][point - 1];
+
+			}
+
+			/* Highest gain curve -> min power */
+			if (pdg == 0)
+				chinfo[pier].min_pwr = pd->pd_pwr[0];
+
+			/* Lowest gain curve -> max power */
+			if (pdg == ee->ee_pd_gains[mode] - 1)
+				chinfo[pier].max_pwr =
+					pd->pd_pwr[pd->pd_points - 1];
+		}
+	}
+
+	return 0;
+}
+
+/* Parse EEPROM data */
 static int
 ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 {
 	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
-	struct ath5k_chan_pcal_info_rf2413 *chan_pcal_info;
-	struct ath5k_chan_pcal_info *gen_chan_info;
-	unsigned int i, c;
+	struct ath5k_chan_pcal_info_rf2413 *pcinfo;
+	struct ath5k_chan_pcal_info *chinfo;
+	u8 *pdgain_idx = ee->ee_pdc_to_idx[mode];
 	u32 offset;
-	int ret;
+	int idx, i, ret;
 	u16 val;
 	u8 pd_gains = 0;
 
-	if (ee->ee_x_gain[mode] & 0x1) pd_gains++;
-	if ((ee->ee_x_gain[mode] >> 1) & 0x1) pd_gains++;
-	if ((ee->ee_x_gain[mode] >> 2) & 0x1) pd_gains++;
-	if ((ee->ee_x_gain[mode] >> 3) & 0x1) pd_gains++;
+	/* Count how many curves we have and
+	 * identify them (which one of the 4
+	 * available curves we have on each count).
+	 * Curves are stored from higher to
+	 * lower gain so we go backwards */
+	for (idx = AR5K_EEPROM_N_PD_CURVES - 1; idx >= 0; idx--) {
+		/* ee_x_gain[mode] is x gain mask */
+		if ((ee->ee_x_gain[mode] >> idx) & 0x1)
+			pdgain_idx[pd_gains++] = idx;
+
+	}
 	ee->ee_pd_gains[mode] = pd_gains;
 
+	if (pd_gains == 0)
+		return -EINVAL;
+
 	offset = ath5k_cal_data_offset_2413(ee, mode);
-	ee->ee_n_piers[mode] = 0;
 	switch (mode) {
 	case AR5K_EEPROM_MODE_11A:
 		if (!AR5K_EEPROM_HDR_11A(ee->ee_header))
@@ -945,7 +1249,7 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 
 		ath5k_eeprom_init_11a_pcal_freq(ah, offset);
 		offset += AR5K_EEPROM_N_5GHZ_CHAN / 2;
-		gen_chan_info = ee->ee_pwr_cal_a;
+		chinfo = ee->ee_pwr_cal_a;
 		break;
 	case AR5K_EEPROM_MODE_11B:
 		if (!AR5K_EEPROM_HDR_11B(ee->ee_header))
@@ -953,7 +1257,7 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 
 		ath5k_eeprom_init_11bg_2413(ah, mode, offset);
 		offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
-		gen_chan_info = ee->ee_pwr_cal_b;
+		chinfo = ee->ee_pwr_cal_b;
 		break;
 	case AR5K_EEPROM_MODE_11G:
 		if (!AR5K_EEPROM_HDR_11G(ee->ee_header))
@@ -961,41 +1265,35 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 
 		ath5k_eeprom_init_11bg_2413(ah, mode, offset);
 		offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2;
-		gen_chan_info = ee->ee_pwr_cal_g;
+		chinfo = ee->ee_pwr_cal_g;
 		break;
 	default:
 		return -EINVAL;
 	}
 
-	if (pd_gains == 0)
-		return 0;
-
 	for (i = 0; i < ee->ee_n_piers[mode]; i++) {
-		chan_pcal_info = &gen_chan_info[i].rf2413_info;
+		pcinfo = &chinfo[i].rf2413_info;
 
 		/*
 		 * Read pwr_i, pddac_i and the first
 		 * 2 pd points (pwr, pddac)
 		 */
 		AR5K_EEPROM_READ(offset++, val);
-		chan_pcal_info->pwr_i[0] = val & 0x1f;
-		chan_pcal_info->pddac_i[0] = (val >> 5) & 0x7f;
-		chan_pcal_info->pwr[0][0] =
-					(val >> 12) & 0xf;
+		pcinfo->pwr_i[0] = val & 0x1f;
+		pcinfo->pddac_i[0] = (val >> 5) & 0x7f;
+		pcinfo->pwr[0][0] = (val >> 12) & 0xf;
 
 		AR5K_EEPROM_READ(offset++, val);
-		chan_pcal_info->pddac[0][0] = val & 0x3f;
-		chan_pcal_info->pwr[0][1] = (val >> 6) & 0xf;
-		chan_pcal_info->pddac[0][1] =
-					(val >> 10) & 0x3f;
+		pcinfo->pddac[0][0] = val & 0x3f;
+		pcinfo->pwr[0][1] = (val >> 6) & 0xf;
+		pcinfo->pddac[0][1] = (val >> 10) & 0x3f;
 
 		AR5K_EEPROM_READ(offset++, val);
-		chan_pcal_info->pwr[0][2] = val & 0xf;
-		chan_pcal_info->pddac[0][2] =
-					(val >> 4) & 0x3f;
+		pcinfo->pwr[0][2] = val & 0xf;
+		pcinfo->pddac[0][2] = (val >> 4) & 0x3f;
 
-		chan_pcal_info->pwr[0][3] = 0;
-		chan_pcal_info->pddac[0][3] = 0;
+		pcinfo->pwr[0][3] = 0;
+		pcinfo->pddac[0][3] = 0;
 
 		if (pd_gains > 1) {
 			/*
@@ -1003,44 +1301,36 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 			 * so it only has 2 pd points.
 			 * Continue wih pd gain 1.
 			 */
-			chan_pcal_info->pwr_i[1] = (val >> 10) & 0x1f;
+			pcinfo->pwr_i[1] = (val >> 10) & 0x1f;
 
-			chan_pcal_info->pddac_i[1] = (val >> 15) & 0x1;
+			pcinfo->pddac_i[1] = (val >> 15) & 0x1;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac_i[1] |= (val & 0x3F) << 1;
+			pcinfo->pddac_i[1] |= (val & 0x3F) << 1;
 
-			chan_pcal_info->pwr[1][0] = (val >> 6) & 0xf;
-			chan_pcal_info->pddac[1][0] =
-						(val >> 10) & 0x3f;
+			pcinfo->pwr[1][0] = (val >> 6) & 0xf;
+			pcinfo->pddac[1][0] = (val >> 10) & 0x3f;
 
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr[1][1] = val & 0xf;
-			chan_pcal_info->pddac[1][1] =
-						(val >> 4) & 0x3f;
-			chan_pcal_info->pwr[1][2] =
-						(val >> 10) & 0xf;
-
-			chan_pcal_info->pddac[1][2] =
-						(val >> 14) & 0x3;
+			pcinfo->pwr[1][1] = val & 0xf;
+			pcinfo->pddac[1][1] = (val >> 4) & 0x3f;
+			pcinfo->pwr[1][2] = (val >> 10) & 0xf;
+
+			pcinfo->pddac[1][2] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac[1][2] |=
-						(val & 0xF) << 2;
+			pcinfo->pddac[1][2] |= (val & 0xF) << 2;
 
-			chan_pcal_info->pwr[1][3] = 0;
-			chan_pcal_info->pddac[1][3] = 0;
+			pcinfo->pwr[1][3] = 0;
+			pcinfo->pddac[1][3] = 0;
 		} else if (pd_gains == 1) {
 			/*
 			 * Pd gain 0 is the last one so
 			 * read the extra point.
 			 */
-			chan_pcal_info->pwr[0][3] =
-						(val >> 10) & 0xf;
+			pcinfo->pwr[0][3] = (val >> 10) & 0xf;
 
-			chan_pcal_info->pddac[0][3] =
-						(val >> 14) & 0x3;
+			pcinfo->pddac[0][3] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac[0][3] |=
-						(val & 0xF) << 2;
+			pcinfo->pddac[0][3] |= (val & 0xF) << 2;
 		}
 
 		/*
@@ -1048,105 +1338,65 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
 		 * as above.
 		 */
 		if (pd_gains > 2) {
-			chan_pcal_info->pwr_i[2] = (val >> 4) & 0x1f;
-			chan_pcal_info->pddac_i[2] = (val >> 9) & 0x7f;
+			pcinfo->pwr_i[2] = (val >> 4) & 0x1f;
+			pcinfo->pddac_i[2] = (val >> 9) & 0x7f;
 
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr[2][0] =
-						(val >> 0) & 0xf;
-			chan_pcal_info->pddac[2][0] =
-						(val >> 4) & 0x3f;
-			chan_pcal_info->pwr[2][1] =
-						(val >> 10) & 0xf;
-
-			chan_pcal_info->pddac[2][1] =
-						(val >> 14) & 0x3;
+			pcinfo->pwr[2][0] = (val >> 0) & 0xf;
+			pcinfo->pddac[2][0] = (val >> 4) & 0x3f;
+			pcinfo->pwr[2][1] = (val >> 10) & 0xf;
+
+			pcinfo->pddac[2][1] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac[2][1] |=
-						(val & 0xF) << 2;
+			pcinfo->pddac[2][1] |= (val & 0xF) << 2;
 
-			chan_pcal_info->pwr[2][2] =
-						(val >> 4) & 0xf;
-			chan_pcal_info->pddac[2][2] =
-						(val >> 8) & 0x3f;
+			pcinfo->pwr[2][2] = (val >> 4) & 0xf;
+			pcinfo->pddac[2][2] = (val >> 8) & 0x3f;
 
-			chan_pcal_info->pwr[2][3] = 0;
-			chan_pcal_info->pddac[2][3] = 0;
+			pcinfo->pwr[2][3] = 0;
+			pcinfo->pddac[2][3] = 0;
 		} else if (pd_gains == 2) {
-			chan_pcal_info->pwr[1][3] =
-						(val >> 4) & 0xf;
-			chan_pcal_info->pddac[1][3] =
-						(val >> 8) & 0x3f;
+			pcinfo->pwr[1][3] = (val >> 4) & 0xf;
+			pcinfo->pddac[1][3] = (val >> 8) & 0x3f;
 		}
 
 		if (pd_gains > 3) {
-			chan_pcal_info->pwr_i[3] = (val >> 14) & 0x3;
+			pcinfo->pwr_i[3] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr_i[3] |= ((val >> 0) & 0x7) << 2;
+			pcinfo->pwr_i[3] |= ((val >> 0) & 0x7) << 2;
 
-			chan_pcal_info->pddac_i[3] = (val >> 3) & 0x7f;
-			chan_pcal_info->pwr[3][0] =
-						(val >> 10) & 0xf;
-			chan_pcal_info->pddac[3][0] =
-						(val >> 14) & 0x3;
+			pcinfo->pddac_i[3] = (val >> 3) & 0x7f;
+			pcinfo->pwr[3][0] = (val >> 10) & 0xf;
+			pcinfo->pddac[3][0] = (val >> 14) & 0x3;
 
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac[3][0] |=
-						(val & 0xF) << 2;
-			chan_pcal_info->pwr[3][1] =
-						(val >> 4) & 0xf;
-			chan_pcal_info->pddac[3][1] =
-						(val >> 8) & 0x3f;
-
-			chan_pcal_info->pwr[3][2] =
-						(val >> 14) & 0x3;
+			pcinfo->pddac[3][0] |= (val & 0xF) << 2;
+			pcinfo->pwr[3][1] = (val >> 4) & 0xf;
+			pcinfo->pddac[3][1] = (val >> 8) & 0x3f;
+
+			pcinfo->pwr[3][2] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr[3][2] |=
-						((val >> 0) & 0x3) << 2;
+			pcinfo->pwr[3][2] |= ((val >> 0) & 0x3) << 2;
 
-			chan_pcal_info->pddac[3][2] =
-						(val >> 2) & 0x3f;
-			chan_pcal_info->pwr[3][3] =
-						(val >> 8) & 0xf;
+			pcinfo->pddac[3][2] = (val >> 2) & 0x3f;
+			pcinfo->pwr[3][3] = (val >> 8) & 0xf;
 
-			chan_pcal_info->pddac[3][3] =
-						(val >> 12) & 0xF;
+			pcinfo->pddac[3][3] = (val >> 12) & 0xF;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pddac[3][3] |=
-						((val >> 0) & 0x3) << 4;
+			pcinfo->pddac[3][3] |= ((val >> 0) & 0x3) << 4;
 		} else if (pd_gains == 3) {
-			chan_pcal_info->pwr[2][3] =
-						(val >> 14) & 0x3;
+			pcinfo->pwr[2][3] = (val >> 14) & 0x3;
 			AR5K_EEPROM_READ(offset++, val);
-			chan_pcal_info->pwr[2][3] |=
-						((val >> 0) & 0x3) << 2;
-
-			chan_pcal_info->pddac[2][3] =
-						(val >> 2) & 0x3f;
-		}
+			pcinfo->pwr[2][3] |= ((val >> 0) & 0x3) << 2;
 
-		for (c = 0; c < pd_gains; c++) {
-			/* Recreate pwr table for this channel using pwr steps */
-			chan_pcal_info->pwr[c][0] += chan_pcal_info->pwr_i[c] * 2;
-			chan_pcal_info->pwr[c][1] += chan_pcal_info->pwr[c][0];
-			chan_pcal_info->pwr[c][2] += chan_pcal_info->pwr[c][1];
-			chan_pcal_info->pwr[c][3] += chan_pcal_info->pwr[c][2];
-			if (chan_pcal_info->pwr[c][3] == chan_pcal_info->pwr[c][2])
-				chan_pcal_info->pwr[c][3] = 0;
-
-			/* Recreate pddac table for this channel using pddac steps */
-			chan_pcal_info->pddac[c][0] += chan_pcal_info->pddac_i[c];
-			chan_pcal_info->pddac[c][1] += chan_pcal_info->pddac[c][0];
-			chan_pcal_info->pddac[c][2] += chan_pcal_info->pddac[c][1];
-			chan_pcal_info->pddac[c][3] += chan_pcal_info->pddac[c][2];
-			if (chan_pcal_info->pddac[c][3] == chan_pcal_info->pddac[c][2])
-				chan_pcal_info->pddac[c][3] = 0;
+			pcinfo->pddac[2][3] = (val >> 2) & 0x3f;
 		}
 	}
 
-	return 0;
+	return ath5k_eeprom_convert_pcal_info_2413(ah, mode, chinfo);
 }
 
+
 /*
  * Read per rate target power (this is the maximum tx power
  * supported by the card). This info is used when setting
@@ -1154,11 +1404,12 @@ ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode)
  *
  * This also works for v5 EEPROMs.
  */
-static int ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_hw *ah, unsigned int mode)
+static int
+ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_hw *ah, unsigned int mode)
 {
 	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
 	struct ath5k_rate_pcal_info *rate_pcal_info;
-	u16 *rate_target_pwr_num;
+	u8 *rate_target_pwr_num;
 	u32 offset;
 	u16 val;
 	int ret, i;
@@ -1264,7 +1515,9 @@ ath5k_eeprom_read_pcal_info(struct ath5k_hw *ah)
 	else
 		read_pcal = ath5k_eeprom_read_pcal_info_5111;
 
-	for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) {
+
+	for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G;
+	mode++) {
 		err = read_pcal(ah, mode);
 		if (err)
 			return err;
@@ -1277,6 +1530,62 @@ ath5k_eeprom_read_pcal_info(struct ath5k_hw *ah)
 	return 0;
 }
 
+static int
+ath5k_eeprom_free_pcal_info(struct ath5k_hw *ah, int mode)
+{
+	struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+	struct ath5k_chan_pcal_info *chinfo;
+	u8 pier, pdg;
+
+	switch (mode) {
+	case AR5K_EEPROM_MODE_11A:
+		if (!AR5K_EEPROM_HDR_11A(ee->ee_header))
+			return 0;
+		chinfo = ee->ee_pwr_cal_a;
+		break;
+	case AR5K_EEPROM_MODE_11B:
+		if (!AR5K_EEPROM_HDR_11B(ee->ee_header))
+			return 0;
+		chinfo = ee->ee_pwr_cal_b;
+		break;
+	case AR5K_EEPROM_MODE_11G:
+		if (!AR5K_EEPROM_HDR_11G(ee->ee_header))
+			return 0;
+		chinfo = ee->ee_pwr_cal_g;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) {
+		if (!chinfo[pier].pd_curves)
+			continue;
+
+		for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) {
+			struct ath5k_pdgain_info *pd =
+					&chinfo[pier].pd_curves[pdg];
+
+			if (pd != NULL) {
+				kfree(pd->pd_step);
+				kfree(pd->pd_pwr);
+			}
+		}
+
+		kfree(chinfo[pier].pd_curves);
+	}
+
+	return 0;
+}
+
+void
+ath5k_eeprom_detach(struct ath5k_hw *ah)
+{
+	u8 mode;
+
+	for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++)
+		ath5k_eeprom_free_pcal_info(ah, mode);
+}
+
 /* Read conformance test limits used for regulatory control */
 static int
 ath5k_eeprom_read_ctl_info(struct ath5k_hw *ah)
@@ -1457,3 +1766,4 @@ bool ath5k_eeprom_is_hb63(struct ath5k_hw *ah)
 	else
 		return false;
 }
+