|
@@ -8,6 +8,7 @@
|
|
|
* by the Free Software Foundation, incorporated herein by reference.
|
|
|
*/
|
|
|
|
|
|
+#include <linux/bitops.h>
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/mtd/mtd.h>
|
|
|
#include <linux/delay.h>
|
|
@@ -18,12 +19,22 @@
|
|
|
#include "spi.h"
|
|
|
#include "efx.h"
|
|
|
#include "nic.h"
|
|
|
+#include "mcdi.h"
|
|
|
+#include "mcdi_pcol.h"
|
|
|
|
|
|
#define EFX_SPI_VERIFY_BUF_LEN 16
|
|
|
+#define EFX_MCDI_CHUNK_LEN 128
|
|
|
|
|
|
struct efx_mtd_partition {
|
|
|
struct mtd_info mtd;
|
|
|
- size_t offset;
|
|
|
+ union {
|
|
|
+ struct {
|
|
|
+ bool updating;
|
|
|
+ u8 nvram_type;
|
|
|
+ u16 fw_subtype;
|
|
|
+ } mcdi;
|
|
|
+ size_t offset;
|
|
|
+ };
|
|
|
const char *type_name;
|
|
|
char name[IFNAMSIZ + 20];
|
|
|
};
|
|
@@ -56,6 +67,7 @@ struct efx_mtd {
|
|
|
container_of(mtd, struct efx_mtd_partition, mtd)
|
|
|
|
|
|
static int falcon_mtd_probe(struct efx_nic *efx);
|
|
|
+static int siena_mtd_probe(struct efx_nic *efx);
|
|
|
|
|
|
/* SPI utilities */
|
|
|
|
|
@@ -223,9 +235,14 @@ static void efx_mtd_rename_device(struct efx_mtd *efx_mtd)
|
|
|
struct efx_mtd_partition *part;
|
|
|
|
|
|
efx_for_each_partition(part, efx_mtd)
|
|
|
- snprintf(part->name, sizeof(part->name),
|
|
|
- "%s %s", efx_mtd->efx->name,
|
|
|
- part->type_name);
|
|
|
+ if (efx_nic_rev(efx_mtd->efx) >= EFX_REV_SIENA_A0)
|
|
|
+ snprintf(part->name, sizeof(part->name),
|
|
|
+ "%s %s:%02x", efx_mtd->efx->name,
|
|
|
+ part->type_name, part->mcdi.fw_subtype);
|
|
|
+ else
|
|
|
+ snprintf(part->name, sizeof(part->name),
|
|
|
+ "%s %s", efx_mtd->efx->name,
|
|
|
+ part->type_name);
|
|
|
}
|
|
|
|
|
|
static int efx_mtd_probe_device(struct efx_nic *efx, struct efx_mtd *efx_mtd)
|
|
@@ -285,7 +302,10 @@ void efx_mtd_rename(struct efx_nic *efx)
|
|
|
|
|
|
int efx_mtd_probe(struct efx_nic *efx)
|
|
|
{
|
|
|
- return falcon_mtd_probe(efx);
|
|
|
+ if (efx_nic_rev(efx) >= EFX_REV_SIENA_A0)
|
|
|
+ return siena_mtd_probe(efx);
|
|
|
+ else
|
|
|
+ return falcon_mtd_probe(efx);
|
|
|
}
|
|
|
|
|
|
/* Implementation of MTD operations for Falcon */
|
|
@@ -393,3 +413,240 @@ static int falcon_mtd_probe(struct efx_nic *efx)
|
|
|
kfree(efx_mtd);
|
|
|
return rc;
|
|
|
}
|
|
|
+
|
|
|
+/* Implementation of MTD operations for Siena */
|
|
|
+
|
|
|
+static int siena_mtd_read(struct mtd_info *mtd, loff_t start,
|
|
|
+ size_t len, size_t *retlen, u8 *buffer)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part = to_efx_mtd_partition(mtd);
|
|
|
+ struct efx_mtd *efx_mtd = mtd->priv;
|
|
|
+ struct efx_nic *efx = efx_mtd->efx;
|
|
|
+ loff_t offset = start;
|
|
|
+ loff_t end = min_t(loff_t, start + len, mtd->size);
|
|
|
+ size_t chunk;
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ while (offset < end) {
|
|
|
+ chunk = min_t(size_t, end - offset, EFX_MCDI_CHUNK_LEN);
|
|
|
+ rc = efx_mcdi_nvram_read(efx, part->mcdi.nvram_type, offset,
|
|
|
+ buffer, chunk);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+ offset += chunk;
|
|
|
+ buffer += chunk;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ *retlen = offset - start;
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int siena_mtd_erase(struct mtd_info *mtd, loff_t start, size_t len)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part = to_efx_mtd_partition(mtd);
|
|
|
+ struct efx_mtd *efx_mtd = mtd->priv;
|
|
|
+ struct efx_nic *efx = efx_mtd->efx;
|
|
|
+ loff_t offset = start & ~((loff_t)(mtd->erasesize - 1));
|
|
|
+ loff_t end = min_t(loff_t, start + len, mtd->size);
|
|
|
+ size_t chunk = part->mtd.erasesize;
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ if (!part->mcdi.updating) {
|
|
|
+ rc = efx_mcdi_nvram_update_start(efx, part->mcdi.nvram_type);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+ part->mcdi.updating = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The MCDI interface can in fact do multiple erase blocks at once;
|
|
|
+ * but erasing may be slow, so we make multiple calls here to avoid
|
|
|
+ * tripping the MCDI RPC timeout. */
|
|
|
+ while (offset < end) {
|
|
|
+ rc = efx_mcdi_nvram_erase(efx, part->mcdi.nvram_type, offset,
|
|
|
+ chunk);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+ offset += chunk;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int siena_mtd_write(struct mtd_info *mtd, loff_t start,
|
|
|
+ size_t len, size_t *retlen, const u8 *buffer)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part = to_efx_mtd_partition(mtd);
|
|
|
+ struct efx_mtd *efx_mtd = mtd->priv;
|
|
|
+ struct efx_nic *efx = efx_mtd->efx;
|
|
|
+ loff_t offset = start;
|
|
|
+ loff_t end = min_t(loff_t, start + len, mtd->size);
|
|
|
+ size_t chunk;
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ if (!part->mcdi.updating) {
|
|
|
+ rc = efx_mcdi_nvram_update_start(efx, part->mcdi.nvram_type);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+ part->mcdi.updating = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (offset < end) {
|
|
|
+ chunk = min_t(size_t, end - offset, EFX_MCDI_CHUNK_LEN);
|
|
|
+ rc = efx_mcdi_nvram_write(efx, part->mcdi.nvram_type, offset,
|
|
|
+ buffer, chunk);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+ offset += chunk;
|
|
|
+ buffer += chunk;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ *retlen = offset - start;
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int siena_mtd_sync(struct mtd_info *mtd)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part = to_efx_mtd_partition(mtd);
|
|
|
+ struct efx_mtd *efx_mtd = mtd->priv;
|
|
|
+ struct efx_nic *efx = efx_mtd->efx;
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ if (part->mcdi.updating) {
|
|
|
+ part->mcdi.updating = 0;
|
|
|
+ rc = efx_mcdi_nvram_update_finish(efx, part->mcdi.nvram_type);
|
|
|
+ }
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static struct efx_mtd_ops siena_mtd_ops = {
|
|
|
+ .read = siena_mtd_read,
|
|
|
+ .erase = siena_mtd_erase,
|
|
|
+ .write = siena_mtd_write,
|
|
|
+ .sync = siena_mtd_sync,
|
|
|
+};
|
|
|
+
|
|
|
+struct siena_nvram_type_info {
|
|
|
+ int port;
|
|
|
+ const char *name;
|
|
|
+};
|
|
|
+
|
|
|
+static struct siena_nvram_type_info siena_nvram_types[] = {
|
|
|
+ [MC_CMD_NVRAM_TYPE_DISABLED_CALLISTO] = { 0, "sfc_dummy_phy" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_MC_FW] = { 0, "sfc_mcfw" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_MC_FW_BACKUP] = { 0, "sfc_mcfw_backup" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_STATIC_CFG_PORT0] = { 0, "sfc_static_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_STATIC_CFG_PORT1] = { 1, "sfc_static_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_DYNAMIC_CFG_PORT0] = { 0, "sfc_dynamic_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_DYNAMIC_CFG_PORT1] = { 1, "sfc_dynamic_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_EXP_ROM] = { 0, "sfc_exp_rom" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_EXP_ROM_CFG_PORT0] = { 0, "sfc_exp_rom_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_EXP_ROM_CFG_PORT1] = { 1, "sfc_exp_rom_cfg" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_PHY_PORT0] = { 0, "sfc_phy_fw" },
|
|
|
+ [MC_CMD_NVRAM_TYPE_PHY_PORT1] = { 1, "sfc_phy_fw" },
|
|
|
+};
|
|
|
+
|
|
|
+static int siena_mtd_probe_partition(struct efx_nic *efx,
|
|
|
+ struct efx_mtd *efx_mtd,
|
|
|
+ unsigned int part_id,
|
|
|
+ unsigned int type)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part = &efx_mtd->part[part_id];
|
|
|
+ struct siena_nvram_type_info *info;
|
|
|
+ size_t size, erase_size;
|
|
|
+ bool protected;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ if (type >= ARRAY_SIZE(siena_nvram_types))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ info = &siena_nvram_types[type];
|
|
|
+
|
|
|
+ if (info->port != efx_port_num(efx))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ rc = efx_mcdi_nvram_info(efx, type, &size, &erase_size, &protected);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+ if (protected)
|
|
|
+ return -ENODEV; /* hide it */
|
|
|
+
|
|
|
+ part->mcdi.nvram_type = type;
|
|
|
+ part->type_name = info->name;
|
|
|
+
|
|
|
+ part->mtd.type = MTD_NORFLASH;
|
|
|
+ part->mtd.flags = MTD_CAP_NORFLASH;
|
|
|
+ part->mtd.size = size;
|
|
|
+ part->mtd.erasesize = erase_size;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int siena_mtd_get_fw_subtypes(struct efx_nic *efx,
|
|
|
+ struct efx_mtd *efx_mtd)
|
|
|
+{
|
|
|
+ struct efx_mtd_partition *part;
|
|
|
+ uint16_t fw_subtype_list[MC_CMD_GET_BOARD_CFG_OUT_FW_SUBTYPE_LIST_LEN /
|
|
|
+ sizeof(uint16_t)];
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ rc = efx_mcdi_get_board_cfg(efx, NULL, fw_subtype_list);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+
|
|
|
+ efx_for_each_partition(part, efx_mtd)
|
|
|
+ part->mcdi.fw_subtype = fw_subtype_list[part->mcdi.nvram_type];
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int siena_mtd_probe(struct efx_nic *efx)
|
|
|
+{
|
|
|
+ struct efx_mtd *efx_mtd;
|
|
|
+ int rc = -ENODEV;
|
|
|
+ u32 nvram_types;
|
|
|
+ unsigned int type;
|
|
|
+
|
|
|
+ ASSERT_RTNL();
|
|
|
+
|
|
|
+ rc = efx_mcdi_nvram_types(efx, &nvram_types);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+
|
|
|
+ efx_mtd = kzalloc(sizeof(*efx_mtd) +
|
|
|
+ hweight32(nvram_types) * sizeof(efx_mtd->part[0]),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!efx_mtd)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ efx_mtd->name = "Siena NVRAM manager";
|
|
|
+
|
|
|
+ efx_mtd->ops = &siena_mtd_ops;
|
|
|
+
|
|
|
+ type = 0;
|
|
|
+ efx_mtd->n_parts = 0;
|
|
|
+
|
|
|
+ while (nvram_types != 0) {
|
|
|
+ if (nvram_types & 1) {
|
|
|
+ rc = siena_mtd_probe_partition(efx, efx_mtd,
|
|
|
+ efx_mtd->n_parts, type);
|
|
|
+ if (rc == 0)
|
|
|
+ efx_mtd->n_parts++;
|
|
|
+ else if (rc != -ENODEV)
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ type++;
|
|
|
+ nvram_types >>= 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ rc = siena_mtd_get_fw_subtypes(efx, efx_mtd);
|
|
|
+ if (rc)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ rc = efx_mtd_probe_device(efx, efx_mtd);
|
|
|
+fail:
|
|
|
+ if (rc)
|
|
|
+ kfree(efx_mtd);
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|