Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / rtl8712 / rtl8712_efuse.c
diff --git a/kernel/drivers/staging/rtl8712/rtl8712_efuse.c b/kernel/drivers/staging/rtl8712/rtl8712_efuse.c
new file mode 100644 (file)
index 0000000..d957169
--- /dev/null
@@ -0,0 +1,574 @@
+/*
+ * rtl8712_efuse.c
+ *
+ * Copyright(c) 2007 - 2010 Realtek Corporation. All rights reserved.
+ * Linux device driver for RTL8192SU
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * Modifications for inclusion into the Linux staging tree are
+ * Copyright(c) 2010 Larry Finger. All rights reserved.
+ *
+ * Contact information:
+ * WLAN FAE <wlanfae@realtek.com>.
+ * Larry Finger <Larry.Finger@lwfinger.net>
+ *
+ ******************************************************************************/
+
+#define _RTL8712_EFUSE_C_
+
+#include "osdep_service.h"
+#include "drv_types.h"
+#include "rtl8712_efuse.h"
+
+/* reserve 3 bytes for HW stop read */
+static int efuse_available_max_size = EFUSE_MAX_SIZE - 3 /*0x1FD*/;
+
+static void efuse_reg_ctrl(struct _adapter *padapter, u8 bPowerOn)
+{
+       u8 tmpu8 = 0;
+
+       if (true == bPowerOn) {
+               /* -----------------e-fuse pwr & clk reg ctrl ---------------
+                * Enable LDOE25 Macro Block
+                */
+               tmpu8 = r8712_read8(padapter, EFUSE_TEST + 3);
+               tmpu8 |= 0x80;
+               r8712_write8(padapter, EFUSE_TEST + 3, tmpu8);
+               msleep(20); /* for some platform , need some delay time */
+               /* Change Efuse Clock for write action to 40MHZ */
+               r8712_write8(padapter, EFUSE_CLK_CTRL, 0x03);
+               msleep(20); /* for some platform , need some delay time */
+       } else {
+               /* -----------------e-fuse pwr & clk reg ctrl -----------------
+                * Disable LDOE25 Macro Block
+                */
+               tmpu8 = r8712_read8(padapter, EFUSE_TEST + 3);
+               tmpu8 &= 0x7F;
+               r8712_write8(padapter, EFUSE_TEST + 3, tmpu8);
+               /* Change Efuse Clock for write action to 500K */
+               r8712_write8(padapter, EFUSE_CLK_CTRL, 0x02);
+       }
+}
+
+/*
+ * Before write E-Fuse, this function must be called.
+ */
+u8 r8712_efuse_reg_init(struct _adapter *padapter)
+{
+       return true;
+}
+
+void r8712_efuse_reg_uninit(struct _adapter *padapter)
+{
+       efuse_reg_ctrl(padapter, false);
+}
+
+static u8 efuse_one_byte_read(struct _adapter *padapter, u16 addr, u8 *data)
+{
+       u8 tmpidx = 0, bResult;
+
+       /* -----------------e-fuse reg ctrl --------------------------------- */
+       r8712_write8(padapter, EFUSE_CTRL+1, (u8)(addr&0xFF)); /* address */
+       r8712_write8(padapter, EFUSE_CTRL+2, ((u8)((addr>>8)&0x03)) |
+              (r8712_read8(padapter, EFUSE_CTRL+2)&0xFC));
+       r8712_write8(padapter, EFUSE_CTRL+3, 0x72); /* read cmd */
+       /* wait for complete */
+       while (!(0x80 & r8712_read8(padapter, EFUSE_CTRL+3)) && (tmpidx < 100))
+               tmpidx++;
+       if (tmpidx < 100) {
+               *data = r8712_read8(padapter, EFUSE_CTRL);
+               bResult = true;
+       } else {
+               *data = 0xff;
+               bResult = false;
+       }
+       return bResult;
+}
+
+static u8 efuse_one_byte_write(struct _adapter *padapter, u16 addr, u8 data)
+{
+       u8 tmpidx = 0, bResult;
+
+       /* -----------------e-fuse reg ctrl -------------------------------- */
+       r8712_write8(padapter, EFUSE_CTRL+1, (u8)(addr&0xFF)); /* address */
+       r8712_write8(padapter, EFUSE_CTRL+2, ((u8)((addr>>8)&0x03)) |
+              (r8712_read8(padapter, EFUSE_CTRL+2)&0xFC));
+       r8712_write8(padapter, EFUSE_CTRL, data); /* data */
+       r8712_write8(padapter, EFUSE_CTRL+3, 0xF2); /* write cmd */
+       /* wait for complete */
+       while ((0x80 &  r8712_read8(padapter, EFUSE_CTRL+3)) && (tmpidx < 100))
+               tmpidx++;
+       if (tmpidx < 100)
+               bResult = true;
+       else
+               bResult = false;
+       return bResult;
+}
+
+static u8 efuse_one_byte_rw(struct _adapter *padapter, u8 bRead, u16 addr,
+                           u8 *data)
+{
+       u8 tmpidx = 0, tmpv8 = 0, bResult;
+
+       /* -----------------e-fuse reg ctrl --------------------------------- */
+       r8712_write8(padapter, EFUSE_CTRL+1, (u8)(addr&0xFF)); /* address */
+       tmpv8 = ((u8)((addr >> 8) & 0x03)) |
+                (r8712_read8(padapter, EFUSE_CTRL + 2) & 0xFC);
+       r8712_write8(padapter, EFUSE_CTRL+2, tmpv8);
+       if (true == bRead) {
+               r8712_write8(padapter, EFUSE_CTRL+3,  0x72); /* read cmd */
+               while (!(0x80 & r8712_read8(padapter, EFUSE_CTRL+3)) &&
+                      (tmpidx < 100))
+                       tmpidx++;
+               if (tmpidx < 100) {
+                       *data = r8712_read8(padapter, EFUSE_CTRL);
+                       bResult = true;
+               } else {
+                       *data = 0;
+                       bResult = false;
+               }
+       } else {
+               r8712_write8(padapter, EFUSE_CTRL, *data); /* data */
+               r8712_write8(padapter, EFUSE_CTRL+3, 0xF2); /* write cmd */
+               while ((0x80 & r8712_read8(padapter, EFUSE_CTRL+3)) &&
+                      (tmpidx < 100))
+                       tmpidx++;
+               if (tmpidx < 100)
+                       bResult = true;
+               else
+                       bResult = false;
+       }
+       return bResult;
+}
+
+static u8 efuse_is_empty(struct _adapter *padapter, u8 *empty)
+{
+       u8 value, ret = true;
+
+       /* read one byte to check if E-Fuse is empty */
+       if (efuse_one_byte_rw(padapter, true, 0, &value) == true) {
+               if (0xFF == value)
+                       *empty = true;
+               else
+                       *empty = false;
+       } else
+               ret = false;
+       return ret;
+}
+
+void r8712_efuse_change_max_size(struct _adapter *padapter)
+{
+       u16 pre_pg_data_saddr = 0x1FB;
+       u16 i;
+       u16 pre_pg_data_size = 5;
+       u8 pre_pg_data[5];
+
+       for (i = 0; i < pre_pg_data_size; i++)
+               efuse_one_byte_read(padapter, pre_pg_data_saddr + i,
+                                   &pre_pg_data[i]);
+       if ((pre_pg_data[0] == 0x03) && (pre_pg_data[1] == 0x00) &&
+           (pre_pg_data[2] == 0x00) && (pre_pg_data[3] == 0x00) &&
+           (pre_pg_data[4] == 0x0C))
+               efuse_available_max_size -= pre_pg_data_size;
+}
+
+int r8712_efuse_get_max_size(struct _adapter *padapter)
+{
+       return  efuse_available_max_size;
+}
+
+static u8 calculate_word_cnts(const u8 word_en)
+{
+       u8 word_cnts = 0;
+       u8 word_idx;
+
+       for (word_idx = 0; word_idx < PGPKG_MAX_WORDS; word_idx++)
+               if (!(word_en & BIT(word_idx)))
+                       word_cnts++; /* 0 : write enable */
+       return word_cnts;
+}
+
+static void pgpacket_copy_data(const u8 word_en, const u8 *sourdata,
+                              u8 *targetdata)
+{
+       u8 tmpindex = 0;
+       u8 word_idx, byte_idx;
+
+       for (word_idx = 0; word_idx < PGPKG_MAX_WORDS; word_idx++) {
+               if (!(word_en&BIT(word_idx))) {
+                       byte_idx = word_idx * 2;
+                       targetdata[byte_idx] = sourdata[tmpindex++];
+                       targetdata[byte_idx + 1] = sourdata[tmpindex++];
+               }
+       }
+}
+
+u16 r8712_efuse_get_current_size(struct _adapter *padapter)
+{
+       int bContinual = true;
+       u16 efuse_addr = 0;
+       u8 hworden = 0;
+       u8 efuse_data, word_cnts = 0;
+
+       while (bContinual && efuse_one_byte_read(padapter, efuse_addr,
+              &efuse_data) && (efuse_addr < efuse_available_max_size)) {
+               if (efuse_data != 0xFF) {
+                       hworden =  efuse_data & 0x0F;
+                       word_cnts = calculate_word_cnts(hworden);
+                       /* read next header */
+                       efuse_addr = efuse_addr + (word_cnts * 2) + 1;
+               } else
+                       bContinual = false;
+       }
+       return efuse_addr;
+}
+
+u8 r8712_efuse_pg_packet_read(struct _adapter *padapter, u8 offset, u8 *data)
+{
+       u8 hoffset = 0, hworden = 0, word_cnts = 0;
+       u16 efuse_addr = 0;
+       u8 efuse_data;
+       u8 tmpidx = 0;
+       u8 tmpdata[PGPKT_DATA_SIZE];
+       u8 ret = true;
+
+       if (data == NULL)
+               return false;
+       if (offset > 0x0f)
+               return false;
+       memset(data, 0xFF, sizeof(u8)*PGPKT_DATA_SIZE);
+       while (efuse_addr < efuse_available_max_size) {
+               if (efuse_one_byte_read(padapter, efuse_addr, &efuse_data) ==
+                   true) {
+                       if (efuse_data == 0xFF)
+                               break;
+                       hoffset = (efuse_data >> 4) & 0x0F;
+                       hworden =  efuse_data & 0x0F;
+                       word_cnts = calculate_word_cnts(hworden);
+                       if (hoffset == offset) {
+                               memset(tmpdata, 0xFF, PGPKT_DATA_SIZE);
+                               for (tmpidx = 0; tmpidx < word_cnts * 2;
+                                    tmpidx++) {
+                                       if (efuse_one_byte_read(padapter,
+                                           efuse_addr+1+tmpidx, &efuse_data) ==
+                                            true) {
+                                               tmpdata[tmpidx] = efuse_data;
+                                       } else
+                                               ret = false;
+                               }
+                               pgpacket_copy_data(hworden, tmpdata, data);
+                       }
+                       efuse_addr += 1 + (word_cnts*2);
+               } else {
+                       ret = false;
+                       break;
+               }
+       }
+       return ret;
+}
+
+static u8 fix_header(struct _adapter *padapter, u8 header, u16 header_addr)
+{
+       struct PGPKT_STRUCT pkt;
+       u8 offset, word_en, value;
+       u16 addr;
+       int i;
+       u8 ret = true;
+
+       pkt.offset = GET_EFUSE_OFFSET(header);
+       pkt.word_en = GET_EFUSE_WORD_EN(header);
+       addr = header_addr + 1 + calculate_word_cnts(pkt.word_en) * 2;
+       if (addr > efuse_available_max_size)
+               return false;
+       /* retrieve original data */
+       addr = 0;
+       while (addr < header_addr) {
+               if (efuse_one_byte_read(padapter, addr++, &value) == false) {
+                       ret = false;
+                       break;
+               }
+               offset = GET_EFUSE_OFFSET(value);
+               word_en = GET_EFUSE_WORD_EN(value);
+               if (pkt.offset != offset) {
+                       addr += calculate_word_cnts(word_en)*2;
+                       continue;
+               }
+               for (i = 0; i < PGPKG_MAX_WORDS; i++) {
+                       if (BIT(i) & word_en) {
+                               if (BIT(i) & pkt.word_en) {
+                                       if (efuse_one_byte_read(
+                                                       padapter, addr,
+                                                       &value) == true)
+                                               pkt.data[i*2] = value;
+                                       else
+                                               return false;
+                                       if (efuse_one_byte_read(
+                                                       padapter,
+                                                       addr + 1,
+                                                       &value) == true)
+                                               pkt.data[i*2 + 1] =
+                                                       value;
+                                       else
+                                               return false;
+                               }
+                               addr += 2;
+                       }
+               }
+       }
+       if (addr != header_addr)
+               return false;
+       addr++;
+       /* fill original data */
+       for (i = 0; i < PGPKG_MAX_WORDS; i++) {
+               if (BIT(i) & pkt.word_en) {
+                       efuse_one_byte_write(padapter, addr, pkt.data[i*2]);
+                       efuse_one_byte_write(padapter, addr+1,
+                                       pkt.data[i*2 + 1]);
+                       /* additional check */
+                       if (efuse_one_byte_read(padapter, addr, &value)
+                               == false)
+                               ret = false;
+                       else if (pkt.data[i*2] != value) {
+                               ret = false;
+                               if (0xFF == value) /* write again */
+                                       efuse_one_byte_write(padapter, addr,
+                                                       pkt.data[i * 2]);
+                       }
+                       if (efuse_one_byte_read(padapter, addr+1, &value) ==
+                               false)
+                               ret = false;
+                       else if (pkt.data[i*2 + 1] != value) {
+                               ret = false;
+                               if (0xFF == value) /* write again */
+                                       efuse_one_byte_write(padapter, addr+1,
+                                                       pkt.data[i*2 + 1]);
+                       }
+               }
+               addr += 2;
+       }
+       return ret;
+}
+
+u8 r8712_efuse_pg_packet_write(struct _adapter *padapter, const u8 offset,
+                        const u8 word_en, const u8 *data)
+{
+       u8 pg_header = 0;
+       u16 efuse_addr = 0, curr_size = 0;
+       u8 efuse_data, target_word_cnts = 0;
+       static int repeat_times;
+       int sub_repeat;
+       u8 bResult = true;
+
+       /* check if E-Fuse Clock Enable and E-Fuse Clock is 40M */
+       efuse_data = r8712_read8(padapter, EFUSE_CLK_CTRL);
+       if (efuse_data != 0x03)
+               return false;
+       pg_header = MAKE_EFUSE_HEADER(offset, word_en);
+       target_word_cnts = calculate_word_cnts(word_en);
+       repeat_times = 0;
+       efuse_addr = 0;
+       while (efuse_addr < efuse_available_max_size) {
+               curr_size = r8712_efuse_get_current_size(padapter);
+               if ((curr_size + 1 + target_word_cnts * 2) >
+                    efuse_available_max_size)
+                       return false; /*target_word_cnts + pg header(1 byte)*/
+               efuse_addr = curr_size; /* current size is also the last addr*/
+               efuse_one_byte_write(padapter, efuse_addr, pg_header); /*hdr*/
+               sub_repeat = 0;
+               /* check if what we read is what we write */
+               while (efuse_one_byte_read(padapter, efuse_addr,
+                                          &efuse_data) == false) {
+                       if (++sub_repeat > _REPEAT_THRESHOLD_) {
+                               bResult = false; /* continue to blind write */
+                               break; /* continue to blind write */
+                       }
+               }
+               if ((sub_repeat > _REPEAT_THRESHOLD_) ||
+                   (pg_header == efuse_data)) {
+                       /* write header ok OR can't check header(creep) */
+                       u8 i;
+
+                       /* go to next address */
+                       efuse_addr++;
+                       for (i = 0; i < target_word_cnts*2; i++) {
+                               efuse_one_byte_write(padapter,
+                                                    efuse_addr + i,
+                                                    *(data + i));
+                               if (efuse_one_byte_read(padapter,
+                                   efuse_addr + i, &efuse_data) == false)
+                                       bResult = false;
+                               else if (*(data+i) != efuse_data) /* fail */
+                                       bResult = false;
+                       }
+                       break;
+               }
+               /* write header fail */
+               bResult = false;
+               if (0xFF == efuse_data)
+                       return bResult; /* nothing damaged. */
+               /* call rescue procedure */
+               if (!fix_header(padapter, efuse_data, efuse_addr))
+                       return false; /* rescue fail */
+
+               if (++repeat_times > _REPEAT_THRESHOLD_) /* fail */
+                       break;
+               /* otherwise, take another risk... */
+       }
+       return bResult;
+}
+
+u8 r8712_efuse_access(struct _adapter *padapter, u8 bRead, u16 start_addr,
+                     u16 cnts, u8 *data)
+{
+       int i;
+       u8 res = true;
+
+       if (start_addr > EFUSE_MAX_SIZE)
+               return false;
+       if ((bRead == false) && ((start_addr + cnts) >
+          efuse_available_max_size))
+               return false;
+       if ((false == bRead) && (r8712_efuse_reg_init(padapter) == false))
+               return false;
+       /* -----------------e-fuse one byte read / write ---------------------*/
+       for (i = 0; i < cnts; i++) {
+               if ((start_addr + i) > EFUSE_MAX_SIZE) {
+                       res = false;
+                       break;
+               }
+               res = efuse_one_byte_rw(padapter, bRead, start_addr + i,
+                     data + i);
+               if ((false == bRead) && (false == res))
+                       break;
+       }
+       if (false == bRead)
+               r8712_efuse_reg_uninit(padapter);
+       return res;
+}
+
+u8 r8712_efuse_map_read(struct _adapter *padapter, u16 addr, u16 cnts, u8 *data)
+{
+       u8 offset, ret = true;
+       u8 pktdata[PGPKT_DATA_SIZE];
+       int i, idx;
+
+       if ((addr + cnts) > EFUSE_MAP_MAX_SIZE)
+               return false;
+       if ((efuse_is_empty(padapter, &offset) == true) && (offset ==
+            true)) {
+               for (i = 0; i < cnts; i++)
+                       data[i] = 0xFF;
+               return ret;
+       }
+       offset = (addr >> 3) & 0xF;
+       ret = r8712_efuse_pg_packet_read(padapter, offset, pktdata);
+       i = addr & 0x7; /* pktdata index */
+       idx = 0;        /* data index */
+
+       do {
+               for (; i < PGPKT_DATA_SIZE; i++) {
+                       data[idx++] = pktdata[i];
+                       if (idx == cnts)
+                               return ret;
+               }
+               offset++;
+               if (!r8712_efuse_pg_packet_read(padapter, offset, pktdata))
+                       ret = false;
+               i = 0;
+       } while (1);
+       return ret;
+}
+
+u8 r8712_efuse_map_write(struct _adapter *padapter, u16 addr, u16 cnts,
+                        u8 *data)
+{
+       u8 offset, word_en, empty;
+       u8 pktdata[PGPKT_DATA_SIZE], newdata[PGPKT_DATA_SIZE];
+       int i, j, idx;
+
+       if ((addr + cnts) > EFUSE_MAP_MAX_SIZE)
+               return false;
+       /* check if E-Fuse Clock Enable and E-Fuse Clock is 40M */
+       empty = r8712_read8(padapter, EFUSE_CLK_CTRL);
+       if (empty != 0x03)
+               return false;
+       if (efuse_is_empty(padapter, &empty) == true) {
+               if (true == empty)
+                       memset(pktdata, 0xFF, PGPKT_DATA_SIZE);
+       } else
+               return false;
+       offset = (addr >> 3) & 0xF;
+       if (empty == false)
+               if (!r8712_efuse_pg_packet_read(padapter, offset, pktdata))
+                       return false;
+       word_en = 0xF;
+       memset(newdata, 0xFF, PGPKT_DATA_SIZE);
+       i = addr & 0x7; /* pktdata index */
+       j = 0;          /* newdata index */
+       idx = 0;        /* data index */
+
+       if (i & 0x1) {
+               /*  odd start */
+               if (data[idx] != pktdata[i]) {
+                       word_en &= ~BIT(i >> 1);
+                       newdata[j++] = pktdata[i - 1];
+                       newdata[j++] = data[idx];
+               }
+               i++;
+               idx++;
+       }
+       do {
+               for (; i < PGPKT_DATA_SIZE; i += 2) {
+                       if ((cnts - idx) == 1) {
+                               if (data[idx] != pktdata[i]) {
+                                       word_en &= ~BIT(i >> 1);
+                                       newdata[j++] = data[idx];
+                                       newdata[j++] = pktdata[1 + 1];
+                               }
+                               idx++;
+                               break;
+                       }
+
+                       if ((data[idx] != pktdata[i]) || (data[idx+1] !=
+                            pktdata[i+1])) {
+                               word_en &= ~BIT(i >> 1);
+                               newdata[j++] = data[idx];
+                               newdata[j++] = data[idx + 1];
+                       }
+                       idx += 2;
+
+                       if (idx == cnts)
+                               break;
+               }
+
+               if (word_en != 0xF)
+                       if (r8712_efuse_pg_packet_write(padapter, offset,
+                           word_en, newdata) == false)
+                               return false;
+               if (idx == cnts)
+                       break;
+               offset++;
+               if (empty == false)
+                       if (!r8712_efuse_pg_packet_read(padapter, offset,
+                           pktdata))
+                               return false;
+               i = 0;
+               j = 0;
+               word_en = 0xF;
+               memset(newdata, 0xFF, PGPKT_DATA_SIZE);
+       } while (1);
+
+       return true;
+}