/* * 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 hoffset = 0, 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) { hoffset = (efuse_data >> 4) & 0x0F; 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; } else { /* write header fail */ bResult = false; if (0xFF == efuse_data) return bResult; /* nothing damaged. */ /* call rescue procedure */ if (fix_header(padapter, efuse_data, efuse_addr) == false) 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; } else { 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; }