[PATCH v2 22/22] e1000: Expose i210's external flash as MTD

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx>
---
 drivers/net/e1000/e1000.h  |  32 +++-
 drivers/net/e1000/eeprom.c | 388 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/net/e1000/main.c   |   6 +
 3 files changed, 423 insertions(+), 3 deletions(-)

diff --git a/drivers/net/e1000/e1000.h b/drivers/net/e1000/e1000.h
index 5570432..e6b493c 100644
--- a/drivers/net/e1000/e1000.h
+++ b/drivers/net/e1000/e1000.h
@@ -18,6 +18,7 @@
 
 #include <io.h>
 #include <net.h>
+#include <linux/mtd/mtd.h>
 
 #ifndef _E1000_HW_H_
 #define _E1000_HW_H_
@@ -447,8 +448,11 @@ struct e1000_tx_desc {
 #define E1000_EEWR     (E1000_MIGHT_BE_REMAPPED | 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 */
@@ -691,7 +695,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;
@@ -805,6 +809,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
@@ -2096,6 +2102,27 @@ 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_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)
 
 
 #define E1000_INVM_TEST(n)		(0x122A0 + 4 * (n))
@@ -2141,6 +2168,8 @@ struct e1000_hw {
 		int line;
 	} invm;
 
+	struct mtd_info mtd;
+
 	uint32_t phy_id;
 	uint32_t phy_revision;
 	uint32_t original_fc;
@@ -2183,5 +2212,6 @@ int e1000_poll_reg(struct e1000_hw *hw, uint32_t reg,
 		   uint32_t mask, uint32_t value,
 		   uint64_t 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 55ccf37..1a0c6e1 100644
--- a/drivers/net/e1000/eeprom.c
+++ b/drivers/net/e1000/eeprom.c
@@ -1,6 +1,8 @@
 #include <common.h>
 #include <init.h>
 #include <malloc.h>
+#include <linux/math64.h>
+#include <linux/sizes.h>
 
 #include "e1000.h"
 
@@ -406,9 +408,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, E1000_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 {
@@ -685,6 +707,259 @@ 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, E1000_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, E1000_FLSWCTL);
+	if (!(flswctl & E1000_FLSWCTL_CMDV)) {
+		dev_err(hw->dev, "FLSWCTL.CMDV was cleared");
+		return -EIO;
+	}
+
+	return E1000_SUCCESS;
+}
+
+static void e1000_flash_cmd(struct e1000_hw *hw,
+			    uint32_t cmd, uint32_t offset)
+{
+	uint32_t flswctl = e1000_read_reg(hw, E1000_FLSWCTL);
+	flswctl &= ~E1000_FLSWCTL_CMD_ADDR_MASK;
+	flswctl |= E1000_FLSWCTL_CMD(cmd) | E1000_FLSWCTL_ADDR(offset);
+	e1000_write_reg(hw, E1000_FLSWCTL, flswctl);
+}
+
+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, E1000_FLSWCNT, size);
+	e1000_flash_cmd(hw, E1000_FLSWCTL_CMD_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, E1000_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, E1000_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, E1000_FLSWCNT, size);
+	e1000_flash_cmd(hw, E1000_FLSWCTL_CMD_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, E1000_FLSWDATA, flswdata);
+
+		ret = e1000_flash_mode_check_command_valid(hw);
+		if (ret < 0)
+			return -EIO;
+
+		ret = e1000_poll_reg(hw, E1000_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, E1000_FLSWCTL_CMD_ERASE_DEVICE, 0);
+	else
+		e1000_flash_cmd(hw, E1000_FLSWCTL_CMD_ERASE_SECTOR, offset);
+
+	ret = e1000_flash_mode_check_command_valid(hw);
+	if (ret < 0)
+		return -EIO;
+
+	ret = e1000_poll_reg(hw, E1000_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, E1000_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.
  *
@@ -1037,6 +1312,90 @@ static struct file_operations e1000_invm_ops = {
 	.lseek	= dev_lseek_default,
 };
 
+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;
@@ -1079,7 +1438,32 @@ int e1000_register_eeprom(struct e1000_hw *hw)
 			devfs_remove(&hw->invm.cdev);
 			break;
 		}
+		break;
+	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;
 	}
 
diff --git a/drivers/net/e1000/main.c b/drivers/net/e1000/main.c
index df9ae73..77bcd17 100644
--- a/drivers/net/e1000/main.c
+++ b/drivers/net/e1000/main.c
@@ -3588,6 +3588,12 @@ static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		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.5


_______________________________________________
barebox mailing list
barebox@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/barebox



[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux