qlcnic: 83xx adpater flash interface routines
authorSony Chacko <sony.chacko@qlogic.com>
Tue, 1 Jan 2013 03:20:23 +0000 (03:20 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 2 Jan 2013 10:43:27 +0000 (02:43 -0800)
83xx adapter flash memory map, data structures and interface routines

Signed-off-by: Himanshu Madhani <himanshu.madhani@qlogic.com>
Signed-off-by: Sony Chacko <sony.chacko@qlogic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qlogic/qlcnic/qlcnic.h
drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_hw.c
drivers/net/ethernet/qlogic/qlcnic/qlcnic_83xx_hw.h

index 6d51c24..f0bec7e 100644 (file)
@@ -204,6 +204,7 @@ struct uni_data_desc{
 
 /* Flash Defines and Structures */
 #define QLCNIC_FLT_LOCATION    0x3F1000
+#define QLCNIC_FDT_LOCATION     0x3F0000
 #define QLCNIC_B0_FW_IMAGE_REGION 0x74
 #define QLCNIC_C0_FW_IMAGE_REGION 0x97
 #define QLCNIC_BOOTLD_REGION    0X72
@@ -224,6 +225,36 @@ struct qlcnic_flt_entry {
        u32 end_addr;
 };
 
+/* Flash Descriptor Table */
+struct qlcnic_fdt {
+       u32     valid;
+       u16     ver;
+       u16     len;
+       u16     cksum;
+       u16     unused;
+       u8      model[16];
+       u16     mfg_id;
+       u16     id;
+       u8      flag;
+       u8      erase_cmd;
+       u8      alt_erase_cmd;
+       u8      write_enable_cmd;
+       u8      write_enable_bits;
+       u8      write_statusreg_cmd;
+       u8      unprotected_sec_cmd;
+       u8      read_manuf_cmd;
+       u32     block_size;
+       u32     alt_block_size;
+       u32     flash_size;
+       u32     write_enable_data;
+       u8      readid_addr_len;
+       u8      write_disable_bits;
+       u8      read_dev_id_len;
+       u8      chip_erase_cmd;
+       u16     read_timeo;
+       u8      protected_sec_cmd;
+       u8      resvd[65];
+};
 /* Magic number to let user know flash is programmed */
 #define        QLCNIC_BDINFO_MAGIC 0x12345678
 
@@ -404,6 +435,7 @@ struct qlcnic_hardware_context {
        struct qlcnic_hardware_ops *hw_ops;
        struct qlcnic_nic_intr_coalesce coal;
        struct qlcnic_fw_dump fw_dump;
+       struct qlcnic_fdt fdt;
        struct qlcnic_intrpt_config *intr_tbl;
        u32 *reg_tbl;
        u32 *ext_reg_tbl;
@@ -972,6 +1004,7 @@ struct qlcnic_adapter {
        u64 dev_rst_time;
        u8 mac_learn;
        unsigned long vlans[BITS_TO_LONGS(VLAN_N_VID)];
+       u8 flash_mfg_id;
        struct qlcnic_npar_info *npars;
        struct qlcnic_eswitch *eswitch;
        struct qlcnic_nic_template *nic_ops;
index 6d4d792..a4a521e 100644 (file)
@@ -1782,3 +1782,363 @@ out:
        qlcnic_free_mbx_args(&cmd);
        return err;
 }
+
+int qlcnic_83xx_lock_flash(struct qlcnic_adapter *adapter)
+{
+       int id, timeout = 0;
+       u32 status = 0;
+
+       while (status == 0) {
+               status = QLC_SHARED_REG_RD32(adapter, QLCNIC_FLASH_LOCK);
+               if (status)
+                       break;
+
+               if (++timeout >= QLC_83XX_FLASH_LOCK_TIMEOUT) {
+                       id = QLC_SHARED_REG_RD32(adapter,
+                                                QLCNIC_FLASH_LOCK_OWNER);
+                       dev_err(&adapter->pdev->dev,
+                               "%s: failed, lock held by %d\n", __func__, id);
+                       return -EIO;
+               }
+               usleep_range(1000, 2000);
+       }
+
+       QLC_SHARED_REG_WR32(adapter, QLCNIC_FLASH_LOCK_OWNER, adapter->portnum);
+       return 0;
+}
+
+void qlcnic_83xx_unlock_flash(struct qlcnic_adapter *adapter)
+{
+       QLC_SHARED_REG_RD32(adapter, QLCNIC_FLASH_UNLOCK);
+       QLC_SHARED_REG_WR32(adapter, QLCNIC_FLASH_LOCK_OWNER, 0xFF);
+}
+
+static int qlcnic_83xx_lockless_flash_read32(struct qlcnic_adapter *adapter,
+                                            u32 flash_addr, u8 *p_data,
+                                            int count)
+{
+       int i, ret;
+       u32 word, range, flash_offset, addr = flash_addr;
+       ulong indirect_add, direct_window;
+
+       flash_offset = addr & (QLCNIC_FLASH_SECTOR_SIZE - 1);
+       if (addr & 0x3) {
+               dev_err(&adapter->pdev->dev, "Illegal addr = 0x%x\n", addr);
+               return -EIO;
+       }
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_DIRECT_WINDOW,
+                                    (addr));
+
+       range = flash_offset + (count * sizeof(u32));
+       /* Check if data is spread across multiple sectors */
+       if (range > (QLCNIC_FLASH_SECTOR_SIZE - 1)) {
+
+               /* Multi sector read */
+               for (i = 0; i < count; i++) {
+                       indirect_add = QLC_83XX_FLASH_DIRECT_DATA(addr);
+                       ret = qlcnic_83xx_rd_reg_indirect(adapter,
+                                                         indirect_add);
+                       if (ret == -EIO)
+                               return -EIO;
+
+                       word = ret;
+                       *(u32 *)p_data  = word;
+                       p_data = p_data + 4;
+                       addr = addr + 4;
+                       flash_offset = flash_offset + 4;
+
+                       if (flash_offset > (QLCNIC_FLASH_SECTOR_SIZE - 1)) {
+                               direct_window = QLC_83XX_FLASH_DIRECT_WINDOW;
+                               /* This write is needed once for each sector */
+                               qlcnic_83xx_wrt_reg_indirect(adapter,
+                                                            direct_window,
+                                                            (addr));
+                               flash_offset = 0;
+                       }
+               }
+       } else {
+               /* Single sector read */
+               for (i = 0; i < count; i++) {
+                       indirect_add = QLC_83XX_FLASH_DIRECT_DATA(addr);
+                       ret = qlcnic_83xx_rd_reg_indirect(adapter,
+                                                         indirect_add);
+                       if (ret == -EIO)
+                               return -EIO;
+
+                       word = ret;
+                       *(u32 *)p_data  = word;
+                       p_data = p_data + 4;
+                       addr = addr + 4;
+               }
+       }
+
+       return 0;
+}
+
+static int qlcnic_83xx_poll_flash_status_reg(struct qlcnic_adapter *adapter)
+{
+       u32 status;
+       int retries = QLC_83XX_FLASH_READ_RETRY_COUNT;
+
+       do {
+               status = qlcnic_83xx_rd_reg_indirect(adapter,
+                                                    QLC_83XX_FLASH_STATUS);
+               if ((status & QLC_83XX_FLASH_STATUS_READY) ==
+                   QLC_83XX_FLASH_STATUS_READY)
+                       break;
+
+               msleep(QLC_83XX_FLASH_STATUS_REG_POLL_DELAY);
+       } while (--retries);
+
+       if (!retries)
+               return -EIO;
+
+       return 0;
+}
+
+static int qlcnic_83xx_enable_flash_write_op(struct qlcnic_adapter *adapter)
+{
+       int ret;
+       u32 cmd;
+       cmd = adapter->ahw->fdt.write_statusreg_cmd;
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    (QLC_83XX_FLASH_FDT_WRITE_DEF_SIG | cmd));
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA,
+                                    adapter->ahw->fdt.write_enable_bits);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_SECOND_ERASE_MS_VAL);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret)
+               return -EIO;
+
+       return 0;
+}
+
+static int qlcnic_83xx_disable_flash_write_op(struct qlcnic_adapter *adapter)
+{
+       int ret;
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    (QLC_83XX_FLASH_FDT_WRITE_DEF_SIG |
+                                    adapter->ahw->fdt.write_statusreg_cmd));
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA,
+                                    adapter->ahw->fdt.write_disable_bits);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_SECOND_ERASE_MS_VAL);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret)
+               return -EIO;
+
+       return 0;
+}
+
+int qlcnic_83xx_read_flash_mfg_id(struct qlcnic_adapter *adapter)
+{
+       int ret, mfg_id;
+
+       if (qlcnic_83xx_lock_flash(adapter))
+               return -EIO;
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    QLC_83XX_FLASH_FDT_READ_MFG_ID_VAL);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_READ_CTRL);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               qlcnic_83xx_unlock_flash(adapter);
+               return -EIO;
+       }
+
+       mfg_id = qlcnic_83xx_rd_reg_indirect(adapter, QLC_83XX_FLASH_RDDATA);
+       if (mfg_id == -EIO)
+               return -EIO;
+
+       adapter->flash_mfg_id = (mfg_id & 0xFF);
+       qlcnic_83xx_unlock_flash(adapter);
+
+       return 0;
+}
+
+int qlcnic_83xx_read_flash_descriptor_table(struct qlcnic_adapter *adapter)
+{
+       int count, fdt_size, ret = 0;
+
+       fdt_size = sizeof(struct qlcnic_fdt);
+       count = fdt_size / sizeof(u32);
+
+       if (qlcnic_83xx_lock_flash(adapter))
+               return -EIO;
+
+       memset(&adapter->ahw->fdt, 0, fdt_size);
+       ret = qlcnic_83xx_lockless_flash_read32(adapter, QLCNIC_FDT_LOCATION,
+                                               (u8 *)&adapter->ahw->fdt,
+                                               count);
+
+       qlcnic_83xx_unlock_flash(adapter);
+       return ret;
+}
+
+int qlcnic_83xx_erase_flash_sector(struct qlcnic_adapter *adapter,
+                                  u32 sector_start_addr)
+{
+       u32 reversed_addr, addr1, addr2, cmd;
+       int ret = -EIO;
+
+       if (qlcnic_83xx_lock_flash(adapter) != 0)
+               return -EIO;
+
+       if (adapter->ahw->fdt.mfg_id == adapter->flash_mfg_id) {
+               ret = qlcnic_83xx_enable_flash_write_op(adapter);
+               if (ret) {
+                       qlcnic_83xx_unlock_flash(adapter);
+                       dev_err(&adapter->pdev->dev,
+                               "%s failed at %d\n",
+                               __func__, __LINE__);
+                       return ret;
+               }
+       }
+
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               qlcnic_83xx_unlock_flash(adapter);
+               dev_err(&adapter->pdev->dev,
+                       "%s: failed at %d\n", __func__, __LINE__);
+               return -EIO;
+       }
+
+       addr1 = (sector_start_addr & 0xFF) << 16;
+       addr2 = (sector_start_addr & 0xFF0000) >> 16;
+       reversed_addr = addr1 | addr2;
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA,
+                                    reversed_addr);
+       cmd = QLC_83XX_FLASH_FDT_ERASE_DEF_SIG | adapter->ahw->fdt.erase_cmd;
+       if (adapter->ahw->fdt.mfg_id == adapter->flash_mfg_id)
+               qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR, cmd);
+       else
+               qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                            QLC_83XX_FLASH_OEM_ERASE_SIG);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_LAST_ERASE_MS_VAL);
+
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               qlcnic_83xx_unlock_flash(adapter);
+               dev_err(&adapter->pdev->dev,
+                       "%s: failed at %d\n", __func__, __LINE__);
+               return -EIO;
+       }
+
+       if (adapter->ahw->fdt.mfg_id == adapter->flash_mfg_id) {
+               ret = qlcnic_83xx_disable_flash_write_op(adapter);
+               if (ret) {
+                       qlcnic_83xx_unlock_flash(adapter);
+                       dev_err(&adapter->pdev->dev,
+                               "%s: failed at %d\n", __func__, __LINE__);
+                       return ret;
+               }
+       }
+
+       qlcnic_83xx_unlock_flash(adapter);
+
+       return 0;
+}
+
+int qlcnic_83xx_flash_write32(struct qlcnic_adapter *adapter, u32 addr,
+                             u32 *p_data)
+{
+       int ret = -EIO;
+       u32 addr1 = 0x00800000 | (addr >> 2);
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR, addr1);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA, *p_data);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_LAST_ERASE_MS_VAL);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               dev_err(&adapter->pdev->dev,
+                       "%s: failed at %d\n", __func__, __LINE__);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+int qlcnic_83xx_flash_bulk_write(struct qlcnic_adapter *adapter, u32 addr,
+                                u32 *p_data, int count)
+{
+       u32 temp;
+       int ret = -EIO;
+
+       if ((count < QLC_83XX_FLASH_BULK_WRITE_MIN) ||
+           (count > QLC_83XX_FLASH_BULK_WRITE_MAX)) {
+               dev_err(&adapter->pdev->dev,
+                       "%s: Invalid word count\n", __func__);
+               return -EIO;
+       }
+
+       temp = qlcnic_83xx_rd_reg_indirect(adapter,
+                                          QLC_83XX_FLASH_SPI_CONTROL);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_SPI_CONTROL,
+                                    (temp | QLC_83XX_FLASH_SPI_CTRL));
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    QLC_83XX_FLASH_ADDR_TEMP_VAL);
+
+       /* First DWORD write */
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA, *p_data++);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_FIRST_MS_PATTERN);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               dev_err(&adapter->pdev->dev,
+                       "%s: failed at %d\n", __func__, __LINE__);
+               return -EIO;
+       }
+
+       count--;
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    QLC_83XX_FLASH_ADDR_SECOND_TEMP_VAL);
+       /* Second to N-1 DWORD writes */
+       while (count != 1) {
+               qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA,
+                                            *p_data++);
+               qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                            QLC_83XX_FLASH_SECOND_MS_PATTERN);
+               ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+               if (ret) {
+                       dev_err(&adapter->pdev->dev,
+                               "%s: failed at %d\n", __func__, __LINE__);
+                       return -EIO;
+               }
+               count--;
+       }
+
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_ADDR,
+                                    QLC_83XX_FLASH_ADDR_TEMP_VAL |
+                                    (addr >> 2));
+       /* Last DWORD write */
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_WRDATA, *p_data++);
+       qlcnic_83xx_wrt_reg_indirect(adapter, QLC_83XX_FLASH_CONTROL,
+                                    QLC_83XX_FLASH_LAST_MS_PATTERN);
+       ret = qlcnic_83xx_poll_flash_status_reg(adapter);
+       if (ret) {
+               dev_err(&adapter->pdev->dev,
+                       "%s: failed at %d\n", __func__, __LINE__);
+               return -EIO;
+       }
+
+       ret = qlcnic_83xx_rd_reg_indirect(adapter, QLC_83XX_FLASH_SPI_STATUS);
+       if ((ret & QLC_83XX_FLASH_SPI_CTRL) == QLC_83XX_FLASH_SPI_CTRL) {
+               dev_err(&adapter->pdev->dev, "%s: failed at %d\n",
+                       __func__, __LINE__);
+               /* Operation failed, clear error bit */
+               temp = qlcnic_83xx_rd_reg_indirect(adapter,
+                                                  QLC_83XX_FLASH_SPI_CONTROL);
+               qlcnic_83xx_wrt_reg_indirect(adapter,
+                                            QLC_83XX_FLASH_SPI_CONTROL,
+                                            (temp | QLC_83XX_FLASH_SPI_CTRL));
+       }
+
+       return 0;
+}
index 7af5a46..4519995 100644 (file)
@@ -134,6 +134,46 @@ struct qlcnic_macvlan_mbx {
 #define QLC_83XX_DEFAULT_MODE                          0x0
 #define QLCNIC_BRDTYPE_83XX_10G                        0x0083
 
+#define QLC_83XX_FLASH_SPI_STATUS              0x2808E010
+#define QLC_83XX_FLASH_SPI_CONTROL             0x2808E014
+#define QLC_83XX_FLASH_STATUS                  0x42100004
+#define QLC_83XX_FLASH_CONTROL                 0x42110004
+#define QLC_83XX_FLASH_ADDR                    0x42110008
+#define QLC_83XX_FLASH_WRDATA                  0x4211000C
+#define QLC_83XX_FLASH_RDDATA                  0x42110018
+#define QLC_83XX_FLASH_DIRECT_WINDOW           0x42110030
+#define QLC_83XX_FLASH_DIRECT_DATA(DATA)       (0x42150000 | (0x0000FFFF&DATA))
+#define QLC_83XX_FLASH_SECTOR_ERASE_CMD        0xdeadbeef
+#define QLC_83XX_FLASH_WRITE_CMD               0xdacdacda
+#define QLC_83XX_FLASH_BULK_WRITE_CMD          0xcadcadca
+#define QLC_83XX_FLASH_READ_RETRY_COUNT        5000
+#define QLC_83XX_FLASH_STATUS_READY            0x6
+#define QLC_83XX_FLASH_BULK_WRITE_MIN          2
+#define QLC_83XX_FLASH_BULK_WRITE_MAX          64
+#define QLC_83XX_FLASH_STATUS_REG_POLL_DELAY   1
+#define QLC_83XX_ERASE_MODE                    1
+#define QLC_83XX_WRITE_MODE                    2
+#define QLC_83XX_BULK_WRITE_MODE               3
+#define QLC_83XX_FLASH_FDT_WRITE_DEF_SIG       0xFD0100
+#define QLC_83XX_FLASH_FDT_ERASE_DEF_SIG       0xFD0300
+#define QLC_83XX_FLASH_FDT_READ_MFG_ID_VAL     0xFD009F
+#define QLC_83XX_FLASH_OEM_ERASE_SIG           0xFD03D8
+#define QLC_83XX_FLASH_OEM_WRITE_SIG           0xFD0101
+#define QLC_83XX_FLASH_OEM_READ_SIG            0xFD0005
+#define QLC_83XX_FLASH_ADDR_TEMP_VAL           0x00800000
+#define QLC_83XX_FLASH_ADDR_SECOND_TEMP_VAL    0x00800001
+#define QLC_83XX_FLASH_WRDATA_DEF              0x0
+#define QLC_83XX_FLASH_READ_CTRL               0x3F
+#define QLC_83XX_FLASH_SPI_CTRL                0x4
+#define QLC_83XX_FLASH_FIRST_ERASE_MS_VAL      0x2
+#define QLC_83XX_FLASH_SECOND_ERASE_MS_VAL     0x5
+#define QLC_83XX_FLASH_LAST_ERASE_MS_VAL       0x3D
+#define QLC_83XX_FLASH_FIRST_MS_PATTERN        0x43
+#define QLC_83XX_FLASH_SECOND_MS_PATTERN       0x7F
+#define QLC_83XX_FLASH_LAST_MS_PATTERN         0x7D
+#define QLC_83xx_FLASH_MAX_WAIT_USEC           100
+#define QLC_83XX_FLASH_LOCK_TIMEOUT            10000
+
 /* Additional registers in 83xx */
 enum qlc_83xx_ext_regs {
        QLCNIC_GLOBAL_RESET = 0,
@@ -253,4 +293,14 @@ void qlcnic_83xx_free_mbx_intr(struct qlcnic_adapter *);
 void qlcnic_83xx_register_map(struct qlcnic_hardware_context *);
 void qlcnic_83xx_idc_aen_work(struct work_struct *);
 void qlcnic_83xx_config_ipaddr(struct qlcnic_adapter *, __be32, int);
+
+int qlcnic_83xx_erase_flash_sector(struct qlcnic_adapter *, u32);
+int qlcnic_83xx_flash_bulk_write(struct qlcnic_adapter *, u32, u32 *, int);
+int qlcnic_83xx_flash_write32(struct qlcnic_adapter *, u32, u32 *);
+int qlcnic_83xx_lock_flash(struct qlcnic_adapter *);
+void qlcnic_83xx_unlock_flash(struct qlcnic_adapter *);
+int qlcnic_83xx_save_flash_status(struct qlcnic_adapter *);
+int qlcnic_83xx_restore_flash_status(struct qlcnic_adapter *, int);
+int qlcnic_83xx_read_flash_mfg_id(struct qlcnic_adapter *);
+int qlcnic_83xx_read_flash_descriptor_table(struct qlcnic_adapter *);
 #endif