9
0
Fork 0

e1000: Expose i210's external flash as MTD

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

Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Andrey Smirnov 2016-06-01 21:58:51 -07:00 committed by Sascha Hauer
parent a74b97f009
commit 4ff3269a70
3 changed files with 423 additions and 3 deletions

View File

@ -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_ */

View File

@ -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;
}

View File

@ -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;