Add code needed to access SPI-NOR flash attached to i210 as a regular
MTD device.

Signed-off-by: Andrey Smirnov <[email protected]>
---
 drivers/net/e1000/e1000.h  |  36 +++-
 drivers/net/e1000/eeprom.c | 398 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/net/e1000/main.c   |   7 +
 3 files changed, 438 insertions(+), 3 deletions(-)

diff --git a/drivers/net/e1000/e1000.h b/drivers/net/e1000/e1000.h
index cb6c914..dc38ce1 100644
--- a/drivers/net/e1000/e1000.h
+++ b/drivers/net/e1000/e1000.h
@@ -17,6 +17,7 @@
  */

 #include <io.h>
+#include <linux/mtd/mtd.h>

 #ifndef _E1000_HW_H_
 #define _E1000_HW_H_
@@ -453,8 +454,11 @@ struct e1000_tx_desc {
 #define E1000_EEWR     0x0102C  /* EEPROM Write Register - RW */
 #define E1000_I210_EEWR     0x12018  /* EEPROM Write Register - RW */
 #define E1000_FLSWCTL  0x01030  /* FLASH control register */
+#define E1000_I210_FLSWCTL 0x12048  /* FLASH control register */
 #define E1000_FLSWDATA 0x01034  /* FLASH data register */
+#define E1000_I210_FLSWDATA 0x1204C  /* FLASH data register */
 #define E1000_FLSWCNT  0x01038  /* FLASH Access Counter */
+#define E1000_I210_FLSWCNT  0x12050  /* FLASH Access Counter */
 #define E1000_FLOP     0x0103C  /* FLASH Opcode Register */
 #define E1000_ERT      0x02008  /* Early Rx Threshold - RW */
 #define E1000_FCRTL    0x02160 /* Flow Control Receive Threshold Low - RW */
@@ -697,7 +701,7 @@ struct e1000_hw;

 struct e1000_eeprom_info {
        e1000_eeprom_type type;
-       uint16_t word_size;
+       size_t word_size;
        uint16_t opcode_bits;
        uint16_t address_bits;
        uint16_t delay_usec;
@@ -811,6 +815,8 @@ struct e1000_eeprom_info {
 #define E1000_EECD_AUPDEN    0x00100000 /* Enable Autonomous FLASH update */
 #define E1000_EECD_SHADV     0x00200000 /* Shadow RAM Data Valid */
 #define E1000_EECD_SEC1VAL   0x00400000 /* Sector One Valid */
+#define E1000_EECD_I210_FLUPD (1 << 23)
+#define E1000_EECD_I210_FLUDONE (1 << 26)
 #define E1000_EECD_SECVAL_SHIFT      22
 #define E1000_STM_OPCODE     0xDB00
 #define E1000_HICR_FW_RESET  0xC0
@@ -2097,6 +2103,28 @@ struct e1000_eeprom_info {
 #define E1000_CTRL_EXT_INT_TIMER_CLR  0x20000000 /* Clear Interrupt timers
                                                        after IMS clear */

+#define E1000_FLA                      0x1201C
+#define E1000_FLA_FL_SIZE_SHIFT                17
+#define E1000_FLA_FL_SIZE_MASK         (0b111 << E1000_FLA_FL_SIZE_SHIFT) /* 
EEprom Size */
+#define E1000_FLA_FL_SIZE_2MB          0b101
+#define E1000_FLA_FL_SIZE_4MB          0b110
+#define E1000_FLA_FL_SIZE_8MB          0b111
+
+
+#define E1000_FLSWCTL_ADDR(a)          ((a) & 0x00FFFFFF)
+#define E1000_FLSWCTL_CMD_READ         0b0000
+#define E1000_FLSWCTL_CMD_WRITE                0b0001
+#define E1000_FLSWCTL_CMD_ERASE_SECTOR 0b0010
+#define E1000_FLSWCTL_CMD_ERASE_DEVICE 0b0011
+#define E1000_FLSWCTL_CMD_(c)          ((0b1111 & (c)) << 24)
+#define E1000_FLSWCTL_CMD(c)           
E1000_FLSWCTL_CMD_(E1000_FLSWCTL_CMD_##c)
+
+#define E1000_FLSWCTL_CMD_ADDR_MASK    0x0FFFFFFF
+
+#define E1000_FLSWCTL_CMDV             (1 << 28)
+#define E1000_FLSWCTL_FLBUSY           (1 << 29)
+#define E1000_FLSWCTL_DONE             (1 << 30)
+#define E1000_FLSWCTL_GLDONE           (1 << 31)

 struct e1000_hw {
        struct eth_device edev;
@@ -2112,6 +2140,7 @@ struct e1000_hw {
        e1000_media_type media_type;
        e1000_fc_type fc;
        struct e1000_eeprom_info eeprom;
+       struct mtd_info mtd;
        uint32_t phy_id;
        uint32_t phy_revision;
        uint32_t original_fc;
@@ -2150,6 +2179,9 @@ static inline uint32_t e1000_true_offset(struct e1000_hw 
*hw, uint32_t reg)
                unsigned int i;

                const struct e1000_fixup_table fixup_table[] = {
+                       { E1000_FLSWCTL,  E1000_I210_FLSWCTL  },
+                       { E1000_FLSWDATA, E1000_I210_FLSWDATA },
+                       { E1000_FLSWCNT,  E1000_I210_FLSWCNT  },
                        { E1000_EEWR,     E1000_I210_EEWR     },
                        { E1000_PHY_CTRL, E1000_I210_PHY_CTRL },
                        { E1000_EEMNGCTL, E1000_I210_EEMNGCTL },
@@ -2197,4 +2229,6 @@ static inline int e1000_poll_reg(struct e1000_hw *hw, 
uint32_t reg,
 #define E1000_POLL_REG(a, reg, mask, value, timeout)   \
        e1000_poll_reg((a), E1000_##reg, (mask), (value), (timeout))

+int e1000_register_eeprom(struct e1000_hw *hw);
+
 #endif /* _E1000_HW_H_ */
diff --git a/drivers/net/e1000/eeprom.c b/drivers/net/e1000/eeprom.c
index aca16f7..4ea7128 100644
--- a/drivers/net/e1000/eeprom.c
+++ b/drivers/net/e1000/eeprom.c
@@ -2,6 +2,8 @@
 #include <init.h>
 #include <net.h>
 #include <malloc.h>
+#include <linux/math64.h>
+#include <linux/sizes.h>

 #include "e1000.h"

@@ -407,9 +409,29 @@ int32_t e1000_init_eeprom_params(struct e1000_hw *hw)
                break;
        case e1000_igb:
                if (eecd & E1000_EECD_I210_FLASH_DETECTED) {
-                       eeprom->type = e1000_eeprom_flash;
-                       eeprom->word_size = 2048;
+                       uint32_t fla;
+
+                       fla  = E1000_READ_REG(hw, FLA);
+                       fla &= E1000_FLA_FL_SIZE_MASK;
+                       fla >>= E1000_FLA_FL_SIZE_SHIFT;
+
+                       switch (fla) {
+                       case E1000_FLA_FL_SIZE_8MB:
+                               eeprom->word_size = SZ_8M / 2;
+                               break;
+                       case E1000_FLA_FL_SIZE_4MB:
+                               eeprom->word_size = SZ_4M / 2;
+                               break;
+                       case E1000_FLA_FL_SIZE_2MB:
+                               eeprom->word_size = SZ_2M / 2;
+                               break;
+                       default:
+                               eeprom->word_size = 2048;
+                               dev_info(hw->dev, "Unprogrammed Flash detected, 
"
+                                        "limiting access to first 4KB\n");
+                       }

+                       eeprom->type = e1000_eeprom_flash;
                        eeprom->acquire = e1000_acquire_eeprom_flash;
                        eeprom->release = e1000_release_eeprom_flash;
                } else {
@@ -686,6 +708,257 @@ static int32_t e1000_spi_eeprom_ready(struct e1000_hw *hw)
        return E1000_SUCCESS;
 }

+static int e1000_flash_mode_wait_for_idle(struct e1000_hw *hw)
+{
+       /* Strictly speaking we need to poll FLSWCTL.DONE only if we
+        * are executing this code after a reset event, but it
+        * shouldn't hurt to do this everytime, besided we need to
+        * poll got FLSWCTL.GLDONE to make sure that back to back
+        * calls to that function work correctly, since we finish
+        * execution by polling only FLSWCTL.DONE */
+
+       const int ret = E1000_POLL_REG(hw, FLSWCTL,
+                                      E1000_FLSWCTL_DONE | 
E1000_FLSWCTL_GLDONE,
+                                      E1000_FLSWCTL_DONE | 
E1000_FLSWCTL_GLDONE,
+                                      SECOND);
+       if (ret < 0)
+               dev_err(hw->dev,
+                       "Timeout waiting for FLSWCTL.DONE to be set\n");
+       return ret;
+}
+
+static int e1000_flash_mode_check_command_valid(struct e1000_hw *hw)
+{
+       const uint32_t flswctl = E1000_READ_REG(hw, FLSWCTL);
+       if (!(flswctl & E1000_FLSWCTL_CMDV)) {
+               dev_err(hw->dev, "FLSWCTL.CMDV was cleared");
+               return -EIO;
+       }
+
+       return E1000_SUCCESS;
+}
+
+#define E1000_FLASH_CMD(hw, cmd, offset)                               \
+       do {                                                            \
+               uint32_t ___flswctl = E1000_READ_REG(hw, FLSWCTL);      \
+               ___flswctl &= ~E1000_FLSWCTL_CMD_ADDR_MASK;             \
+               ___flswctl |= E1000_FLSWCTL_CMD(cmd) | 
E1000_FLSWCTL_ADDR(offset); \
+               E1000_WRITE_REG(hw, FLSWCTL, ___flswctl);               \
+       } while (0)
+
+static int e1000_flash_mode_read_chunk(struct e1000_hw *hw, loff_t offset,
+                                      size_t size, void *data)
+{
+       int ret;
+       size_t chunk, residue = size;
+       uint32_t flswdata;
+
+       DEBUGFUNC();
+
+       if (size > SZ_4K ||
+           E1000_FLSWCTL_ADDR(offset) != offset)
+               return -EINVAL;
+
+       ret = e1000_flash_mode_wait_for_idle(hw);
+       if (ret < 0)
+               return ret;
+
+       E1000_WRITE_REG(hw, FLSWCNT, size);
+       E1000_FLASH_CMD(hw, READ, offset);
+
+       do {
+               ret = e1000_flash_mode_check_command_valid(hw);
+               if (ret < 0)
+                       return -EIO;
+
+               chunk = min(sizeof(flswdata), residue);
+
+               ret = E1000_POLL_REG(hw, FLSWCTL,
+                                    E1000_FLSWCTL_DONE, E1000_FLSWCTL_DONE,
+                                    SECOND);
+               if (ret < 0) {
+                       dev_err(hw->dev,
+                               "Timeout waiting for FLSWCTL.DONE to be set\n");
+                       return ret;
+               }
+
+               flswdata = E1000_READ_REG(hw, FLSWDATA);
+               /*
+                * Readl does le32_to_cpu, so we need to undo that
+                */
+               flswdata = cpu_to_le32(flswdata);
+               memcpy(data, &flswdata, chunk);
+
+               data += chunk;
+               residue -= chunk;
+       } while (residue);
+
+       return E1000_SUCCESS;
+}
+
+static int e1000_flash_mode_write_chunk(struct e1000_hw *hw, loff_t offset,
+                                       size_t size, const void *data)
+{
+       int ret;
+       size_t chunk, residue = size;
+       uint32_t flswdata;
+
+       if (size > 256 ||
+           E1000_FLSWCTL_ADDR(offset) != offset)
+               return -EINVAL;
+
+       ret = e1000_flash_mode_wait_for_idle(hw);
+       if (ret < 0)
+               return ret;
+
+
+       E1000_WRITE_REG(hw, FLSWCNT, size);
+       E1000_FLASH_CMD(hw, WRITE, offset);
+
+       do {
+               chunk = min(sizeof(flswdata), residue);
+               memcpy(&flswdata, data, chunk);
+               /*
+                * writel does cpu_to_le32, so we do the inverse in
+                * order to account for that
+                */
+               flswdata = le32_to_cpu(flswdata);
+               E1000_WRITE_REG(hw, FLSWDATA, flswdata);
+
+               ret = e1000_flash_mode_check_command_valid(hw);
+               if (ret < 0)
+                       return -EIO;
+
+               ret = E1000_POLL_REG(hw, FLSWCTL,
+                                    E1000_FLSWCTL_DONE, E1000_FLSWCTL_DONE,
+                                    SECOND);
+               if (ret < 0) {
+                       dev_err(hw->dev,
+                               "Timeout waiting for FLSWCTL.DONE to be set\n");
+                       return ret;
+               }
+
+               data += chunk;
+               residue -= chunk;
+
+       } while (residue);
+
+       return E1000_SUCCESS;
+}
+
+
+static int e1000_flash_mode_erase_chunk(struct e1000_hw *hw, loff_t offset,
+                                       size_t size)
+{
+       int ret;
+
+       ret = e1000_flash_mode_wait_for_idle(hw);
+       if (ret < 0)
+               return ret;
+
+       if (!size && !offset)
+               E1000_FLASH_CMD(hw, ERASE_DEVICE, 0);
+       else
+               E1000_FLASH_CMD(hw, ERASE_SECTOR, offset);
+
+       ret = e1000_flash_mode_check_command_valid(hw);
+       if (ret < 0)
+               return -EIO;
+
+       ret = E1000_POLL_REG(hw, FLSWCTL,
+                            E1000_FLSWCTL_DONE | E1000_FLSWCTL_FLBUSY,
+                            E1000_FLSWCTL_DONE,
+                            SECOND);
+       if (ret < 0) {
+               dev_err(hw->dev,
+                       "Timeout waiting for FLSWCTL.DONE to be set\n");
+               return ret;
+       }
+
+       return E1000_SUCCESS;
+}
+
+enum {
+       E1000_FLASH_MODE_OP_READ = 0,
+       E1000_FLASH_MODE_OP_WRITE = 1,
+       E1000_FLASH_MODE_OP_ERASE = 2,
+};
+
+
+static int e1000_flash_mode_io(struct e1000_hw *hw, int op, size_t granularity,
+                              loff_t offset, size_t size, void *data)
+{
+       int ret;
+       size_t residue = size;
+
+       do {
+               const size_t chunk = min(granularity, residue);
+
+               switch (op) {
+               case E1000_FLASH_MODE_OP_READ:
+                       ret = e1000_flash_mode_read_chunk(hw, offset,
+                                                         chunk, data);
+                       break;
+               case E1000_FLASH_MODE_OP_WRITE:
+                       ret = e1000_flash_mode_write_chunk(hw, offset,
+                                                          chunk, data);
+                       break;
+               case E1000_FLASH_MODE_OP_ERASE:
+                       ret = e1000_flash_mode_erase_chunk(hw, offset,
+                                                          chunk);
+                       break;
+               default:
+                       return -ENOTSUPP;
+               }
+
+               if (ret < 0)
+                       return ret;
+
+               offset += chunk;
+               residue -= chunk;
+               data += chunk;
+       } while (residue);
+
+       return E1000_SUCCESS;
+}
+
+
+static int e1000_flash_mode_read(struct e1000_hw *hw, loff_t offset,
+                                size_t size, void *data)
+{
+       return e1000_flash_mode_io(hw,
+                                  E1000_FLASH_MODE_OP_READ, SZ_4K,
+                                  offset, size, data);
+}
+
+static int e1000_flash_mode_write(struct e1000_hw *hw, loff_t offset,
+                                 size_t size, const void *data)
+{
+       int ret;
+
+       ret = e1000_flash_mode_io(hw,
+                                 E1000_FLASH_MODE_OP_WRITE, 256,
+                                 offset, size, (void *)data);
+       if (ret < 0)
+               return ret;
+
+       ret = E1000_POLL_REG(hw, FLSWCTL, E1000_FLSWCTL_FLBUSY,
+                            0,  SECOND);
+       if (ret < 0)
+               dev_err(hw->dev, "Timout while waiting for FLSWCTL.FLBUSY\n");
+
+       return ret;
+}
+
+static int e1000_flash_mode_erase(struct e1000_hw *hw, loff_t offset,
+                                 size_t size)
+{
+       return e1000_flash_mode_io(hw,
+                                  E1000_FLASH_MODE_OP_ERASE, SZ_4K,
+                                  offset, size, NULL);
+}
+
+
 /******************************************************************************
  * Reads a 16 bit word from the EEPROM.
  *
@@ -774,3 +1047,124 @@ int e1000_validate_eeprom_checksum(struct e1000_hw *hw)

        return -E1000_ERR_EEPROM;
 }
+
+static int e1000_mtd_read_or_write(bool read,
+                                  struct mtd_info *mtd, loff_t off, size_t len,
+                                  size_t *retlen, u_char *buf)
+{
+       int ret;
+       struct e1000_hw *hw = container_of(mtd, struct e1000_hw, mtd);
+
+       DEBUGFUNC();
+
+       if (e1000_acquire_eeprom(hw) == E1000_SUCCESS) {
+               if (read)
+                       ret = e1000_flash_mode_read(hw, off,
+                                                   len, buf);
+               else
+                       ret = e1000_flash_mode_write(hw, off,
+                                                    len, buf);
+               if (ret == E1000_SUCCESS)
+                       *retlen = len;
+
+               e1000_release_eeprom(hw);
+       } else {
+               ret = -E1000_ERR_EEPROM;
+       }
+
+       return ret;
+
+}
+
+static int e1000_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+                                size_t *retlen, u_char *buf)
+{
+       return e1000_mtd_read_or_write(true,
+                                      mtd, from, len, retlen, buf);
+}
+
+static int e1000_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+                          size_t *retlen, const u_char *buf)
+{
+       return e1000_mtd_read_or_write(false,
+                                      mtd, to, len, retlen, (u_char *)buf);
+}
+
+static int e1000_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+       uint32_t rem;
+       struct e1000_hw *hw = container_of(mtd, struct e1000_hw, mtd);
+       int ret;
+
+       div_u64_rem(instr->len, mtd->erasesize, &rem);
+       if (rem)
+               return -EINVAL;
+
+       ret = e1000_acquire_eeprom(hw);
+       if (ret != E1000_SUCCESS)
+               goto fail;
+
+       /*
+        * If mtd->size is 4096 it means we are dealing with
+        * unprogrammed flash and we don't really know its size to
+        * make an informed decision wheither to erase the whole chip or
+        * just a number of its sectors
+        */
+       if (mtd->size > SZ_4K &&
+           instr->len == mtd->size)
+               ret = e1000_flash_mode_erase(hw, 0, 0);
+       else
+               ret = e1000_flash_mode_erase(hw,
+                                            instr->addr, instr->len);
+
+       e1000_release_eeprom(hw);
+
+       if (ret < 0)
+               goto fail;
+
+       instr->state = MTD_ERASE_DONE;
+       mtd_erase_callback(instr);
+
+       return 0;
+
+fail:
+       instr->state = MTD_ERASE_FAILED;
+       return ret;
+}
+
+int e1000_register_eeprom(struct e1000_hw *hw)
+{
+       int ret = E1000_SUCCESS;
+       struct e1000_eeprom_info *eeprom = &hw->eeprom;
+
+       switch (eeprom->type) {
+       case e1000_eeprom_flash:
+               if (hw->mac_type != e1000_igb)
+                       break;
+
+               hw->mtd.parent = hw->dev;
+               hw->mtd.read = e1000_mtd_read;
+               hw->mtd.write = e1000_mtd_write;
+               hw->mtd.erase = e1000_mtd_erase;
+               hw->mtd.size = eeprom->word_size * 2;
+               hw->mtd.writesize = 1;
+               hw->mtd.subpage_sft = 0;
+
+               hw->mtd.eraseregions = xzalloc(sizeof(struct 
mtd_erase_region_info));
+               hw->mtd.erasesize = SZ_4K;
+               hw->mtd.eraseregions[0].erasesize = SZ_4K;
+               hw->mtd.eraseregions[0].numblocks = hw->mtd.size / SZ_4K;
+               hw->mtd.numeraseregions = 1;
+
+               hw->mtd.flags = MTD_CAP_NORFLASH;
+               hw->mtd.type = MTD_NORFLASH;
+
+               ret = add_mtd_device(&hw->mtd, "e1000-nor",
+                                    DEVICE_ID_DYNAMIC);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
diff --git a/drivers/net/e1000/main.c b/drivers/net/e1000/main.c
index 05d38ac..473d121 100644
--- a/drivers/net/e1000/main.c
+++ b/drivers/net/e1000/main.c
@@ -3588,6 +3588,13 @@ static int e1000_probe(struct pci_dev *pdev, const 
struct pci_device_id *id)
                dev_err(&pdev->dev, "EEPROM is invalid!\n");
                return -EINVAL;
        }
+
+       ret = e1000_register_eeprom(hw);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to register EEPROM devices!\n");
+               return ret;
+       }
+
        if (e1000_validate_eeprom_checksum(hw))
                return -EINVAL;

--
2.5.0

_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to