summaryrefslogtreecommitdiff
path: root/drivers/mtd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/devices/Kconfig11
-rw-r--r--drivers/mtd/devices/Makefile1
-rw-r--r--drivers/mtd/devices/mtd_intel_dg.c830
-rw-r--r--drivers/mtd/nand/qpic_common.c30
-rw-r--r--drivers/mtd/nand/raw/qcom_nandc.c6
-rw-r--r--drivers/mtd/spi-nor/sysfs.c2
6 files changed, 872 insertions, 8 deletions
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index aed653ce8fa2..46cebde79f34 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -183,6 +183,17 @@ config MTD_POWERNV_FLASH
platforms from Linux. This device abstracts away the
firmware interface for flash access.
+config MTD_INTEL_DG
+ tristate "Intel Discrete Graphics non-volatile memory driver"
+ depends on AUXILIARY_BUS
+ depends on MTD
+ help
+ This provides an MTD device to access Intel Discrete Graphics
+ non-volatile memory.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mtd-intel-dg.
+
comment "Disk-On-Chip Device Drivers"
config MTD_DOCG3
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index d11eb2b8b6f8..9fe4ce9cffde 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_SST25L) += sst25l.o
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o
+obj-$(CONFIG_MTD_INTEL_DG) += mtd_intel_dg.o
CFLAGS_docg3.o += -I$(src)
diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c
new file mode 100644
index 000000000000..b438ee5aacc3
--- /dev/null
+++ b/drivers/mtd/devices/mtd_intel_dg.c
@@ -0,0 +1,830 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(c) 2019-2025, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/intel_dg_nvm_aux.h>
+#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+struct intel_dg_nvm {
+ struct kref refcnt;
+ struct mtd_info mtd;
+ struct mutex lock; /* region access lock */
+ void __iomem *base;
+ void __iomem *base2;
+ bool non_posted_erase;
+
+ size_t size;
+ unsigned int nregions;
+ struct {
+ const char *name;
+ u8 id;
+ u64 offset;
+ u64 size;
+ unsigned int is_readable:1;
+ unsigned int is_writable:1;
+ } regions[] __counted_by(nregions);
+};
+
+#define NVM_TRIGGER_REG 0x00000000
+#define NVM_VALSIG_REG 0x00000010
+#define NVM_ADDRESS_REG 0x00000040
+#define NVM_REGION_ID_REG 0x00000044
+#define NVM_DEBUG_REG 0x00000000
+/*
+ * [15:0]-Erase size = 0x0010 4K 0x0080 32K 0x0100 64K
+ * [23:16]-Reserved
+ * [31:24]-Erase MEM RegionID
+ */
+#define NVM_ERASE_REG 0x00000048
+#define NVM_ACCESS_ERROR_REG 0x00000070
+#define NVM_ADDRESS_ERROR_REG 0x00000074
+
+/* Flash Valid Signature */
+#define NVM_FLVALSIG 0x0FF0A55A
+
+#define NVM_MAP_ADDR_MASK GENMASK(7, 0)
+#define NVM_MAP_ADDR_SHIFT 0x00000004
+
+#define NVM_REGION_ID_DESCRIPTOR 0
+/* Flash Region Base Address */
+#define NVM_FRBA 0x40
+/* Flash Region __n - Flash Descriptor Record */
+#define NVM_FLREG(__n) (NVM_FRBA + ((__n) * 4))
+/* Flash Map 1 Register */
+#define NVM_FLMAP1_REG 0x18
+#define NVM_FLMSTR4_OFFSET 0x00C
+
+#define NVM_ACCESS_ERROR_PCIE_MASK 0x7
+
+#define NVM_FREG_BASE_MASK GENMASK(15, 0)
+#define NVM_FREG_ADDR_MASK GENMASK(31, 16)
+#define NVM_FREG_ADDR_SHIFT 12
+#define NVM_FREG_MIN_REGION_SIZE 0xFFF
+
+#define NVM_NON_POSTED_ERASE_DONE BIT(23)
+#define NVM_NON_POSTED_ERASE_DONE_ITER 3000
+
+static inline void idg_nvm_set_region_id(struct intel_dg_nvm *nvm, u8 region)
+{
+ iowrite32((u32)region, nvm->base + NVM_REGION_ID_REG);
+}
+
+static inline u32 idg_nvm_error(struct intel_dg_nvm *nvm)
+{
+ void __iomem *base = nvm->base;
+
+ u32 reg = ioread32(base + NVM_ACCESS_ERROR_REG) & NVM_ACCESS_ERROR_PCIE_MASK;
+
+ /* reset error bits */
+ if (reg)
+ iowrite32(reg, base + NVM_ACCESS_ERROR_REG);
+
+ return reg;
+}
+
+static inline u32 idg_nvm_read32(struct intel_dg_nvm *nvm, u32 address)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ return ioread32(base + NVM_TRIGGER_REG);
+}
+
+static inline u64 idg_nvm_read64(struct intel_dg_nvm *nvm, u32 address)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ return readq(base + NVM_TRIGGER_REG);
+}
+
+static void idg_nvm_write32(struct intel_dg_nvm *nvm, u32 address, u32 data)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ iowrite32(data, base + NVM_TRIGGER_REG);
+}
+
+static void idg_nvm_write64(struct intel_dg_nvm *nvm, u32 address, u64 data)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ writeq(data, base + NVM_TRIGGER_REG);
+}
+
+static int idg_nvm_get_access_map(struct intel_dg_nvm *nvm, u32 *access_map)
+{
+ u32 fmstr4_addr;
+ u32 fmstr4;
+ u32 flmap1;
+ u32 fmba;
+
+ idg_nvm_set_region_id(nvm, NVM_REGION_ID_DESCRIPTOR);
+
+ flmap1 = idg_nvm_read32(nvm, NVM_FLMAP1_REG);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ /* Get Flash Master Baser Address (FMBA) */
+ fmba = (FIELD_GET(NVM_MAP_ADDR_MASK, flmap1) << NVM_MAP_ADDR_SHIFT);
+ fmstr4_addr = fmba + NVM_FLMSTR4_OFFSET;
+
+ fmstr4 = idg_nvm_read32(nvm, fmstr4_addr);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ *access_map = fmstr4;
+ return 0;
+}
+
+/*
+ * Region read/write access encoded in the access map
+ * in the following order from the lower bit:
+ * [3:0] regions 12-15 read state
+ * [7:4] regions 12-15 write state
+ * [19:8] regions 0-11 read state
+ * [31:20] regions 0-11 write state
+ */
+static bool idg_nvm_region_readable(u32 access_map, u8 region)
+{
+ if (region < 12)
+ return access_map & BIT(region + 8); /* [19:8] */
+ else
+ return access_map & BIT(region - 12); /* [3:0] */
+}
+
+static bool idg_nvm_region_writable(u32 access_map, u8 region)
+{
+ if (region < 12)
+ return access_map & BIT(region + 20); /* [31:20] */
+ else
+ return access_map & BIT(region - 8); /* [7:4] */
+}
+
+static int idg_nvm_is_valid(struct intel_dg_nvm *nvm)
+{
+ u32 is_valid;
+
+ idg_nvm_set_region_id(nvm, NVM_REGION_ID_DESCRIPTOR);
+
+ is_valid = idg_nvm_read32(nvm, NVM_VALSIG_REG);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ if (is_valid != NVM_FLVALSIG)
+ return -ENODEV;
+
+ return 0;
+}
+
+static unsigned int idg_nvm_get_region(const struct intel_dg_nvm *nvm, loff_t from)
+{
+ unsigned int i;
+
+ for (i = 0; i < nvm->nregions; i++) {
+ if ((nvm->regions[i].offset + nvm->regions[i].size - 1) >= from &&
+ nvm->regions[i].offset <= from &&
+ nvm->regions[i].size != 0)
+ break;
+ }
+
+ return i;
+}
+
+static ssize_t idg_nvm_rewrite_partial(struct intel_dg_nvm *nvm, loff_t to,
+ loff_t offset, size_t len, const u32 *newdata)
+{
+ u32 data = idg_nvm_read32(nvm, to);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ memcpy((u8 *)&data + offset, newdata, len);
+
+ idg_nvm_write32(nvm, to, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ return len;
+}
+
+static ssize_t idg_write(struct intel_dg_nvm *nvm, u8 region,
+ loff_t to, size_t len, const unsigned char *buf)
+{
+ size_t len_s = len;
+ size_t to_shift;
+ size_t len8;
+ size_t len4;
+ ssize_t ret;
+ size_t to4;
+ size_t i;
+
+ idg_nvm_set_region_id(nvm, region);
+
+ to4 = ALIGN_DOWN(to, sizeof(u32));
+ to_shift = min(sizeof(u32) - ((size_t)to - to4), len);
+ if (to - to4) {
+ ret = idg_nvm_rewrite_partial(nvm, to4, to - to4, to_shift, (u32 *)&buf[0]);
+ if (ret < 0)
+ return ret;
+
+ buf += to_shift;
+ to += to_shift;
+ len_s -= to_shift;
+ }
+
+ if (!IS_ALIGNED(to, sizeof(u64)) &&
+ ((to ^ (to + len_s)) & GENMASK(31, 10))) {
+ /*
+ * Workaround reads/writes across 1k-aligned addresses
+ * (start u32 before 1k, end u32 after)
+ * as this fails on hardware.
+ */
+ u32 data;
+
+ memcpy(&data, &buf[0], sizeof(u32));
+ idg_nvm_write32(nvm, to, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ buf += sizeof(u32);
+ to += sizeof(u32);
+ len_s -= sizeof(u32);
+ }
+
+ len8 = ALIGN_DOWN(len_s, sizeof(u64));
+ for (i = 0; i < len8; i += sizeof(u64)) {
+ u64 data;
+
+ memcpy(&data, &buf[i], sizeof(u64));
+ idg_nvm_write64(nvm, to + i, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ }
+
+ len4 = len_s - len8;
+ if (len4 >= sizeof(u32)) {
+ u32 data;
+
+ memcpy(&data, &buf[i], sizeof(u32));
+ idg_nvm_write32(nvm, to + i, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ i += sizeof(u32);
+ len4 -= sizeof(u32);
+ }
+
+ if (len4 > 0) {
+ ret = idg_nvm_rewrite_partial(nvm, to + i, 0, len4, (u32 *)&buf[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return len;
+}
+
+static ssize_t idg_read(struct intel_dg_nvm *nvm, u8 region,
+ loff_t from, size_t len, unsigned char *buf)
+{
+ size_t len_s = len;
+ size_t from_shift;
+ size_t from4;
+ size_t len8;
+ size_t len4;
+ size_t i;
+
+ idg_nvm_set_region_id(nvm, region);
+
+ from4 = ALIGN_DOWN(from, sizeof(u32));
+ from_shift = min(sizeof(u32) - ((size_t)from - from4), len);
+
+ if (from - from4) {
+ u32 data = idg_nvm_read32(nvm, from4);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[0], (u8 *)&data + (from - from4), from_shift);
+ len_s -= from_shift;
+ buf += from_shift;
+ from += from_shift;
+ }
+
+ if (!IS_ALIGNED(from, sizeof(u64)) &&
+ ((from ^ (from + len_s)) & GENMASK(31, 10))) {
+ /*
+ * Workaround reads/writes across 1k-aligned addresses
+ * (start u32 before 1k, end u32 after)
+ * as this fails on hardware.
+ */
+ u32 data = idg_nvm_read32(nvm, from);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[0], &data, sizeof(data));
+ len_s -= sizeof(u32);
+ buf += sizeof(u32);
+ from += sizeof(u32);
+ }
+
+ len8 = ALIGN_DOWN(len_s, sizeof(u64));
+ for (i = 0; i < len8; i += sizeof(u64)) {
+ u64 data = idg_nvm_read64(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ memcpy(&buf[i], &data, sizeof(data));
+ }
+
+ len4 = len_s - len8;
+ if (len4 >= sizeof(u32)) {
+ u32 data = idg_nvm_read32(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[i], &data, sizeof(data));
+ i += sizeof(u32);
+ len4 -= sizeof(u32);
+ }
+
+ if (len4 > 0) {
+ u32 data = idg_nvm_read32(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[i], &data, len4);
+ }
+
+ return len;
+}
+
+static ssize_t
+idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_addr)
+{
+ void __iomem *base2 = nvm->base2;
+ void __iomem *base = nvm->base;
+ const u32 block = 0x10;
+ u32 iter = 0;
+ u32 reg;
+ u64 i;
+
+ for (i = 0; i < len; i += SZ_4K) {
+ iowrite32(from + i, base + NVM_ADDRESS_REG);
+ iowrite32(region << 24 | block, base + NVM_ERASE_REG);
+ if (nvm->non_posted_erase) {
+ /* Wait for Erase Done */
+ reg = ioread32(base2 + NVM_DEBUG_REG);
+ while (!(reg & NVM_NON_POSTED_ERASE_DONE) &&
+ ++iter < NVM_NON_POSTED_ERASE_DONE_ITER) {
+ msleep(10);
+ reg = ioread32(base2 + NVM_DEBUG_REG);
+ }
+ if (reg & NVM_NON_POSTED_ERASE_DONE) {
+ /* Clear Erase Done */
+ iowrite32(reg, base2 + NVM_DEBUG_REG);
+ } else {
+ *fail_addr = from + i;
+ return -ETIME;
+ }
+ }
+ /* Since the writes are via sgunit
+ * we cannot do back to back erases.
+ */
+ msleep(50);
+ }
+ return len;
+}
+
+static int intel_dg_nvm_init(struct intel_dg_nvm *nvm, struct device *device,
+ bool non_posted_erase)
+{
+ u32 access_map = 0;
+ unsigned int i, n;
+ int ret;
+
+ /* clean error register, previous errors are ignored */
+ idg_nvm_error(nvm);
+
+ ret = idg_nvm_is_valid(nvm);
+ if (ret) {
+ dev_err(device, "The MEM is not valid %d\n", ret);
+ return ret;
+ }
+
+ if (idg_nvm_get_access_map(nvm, &access_map))
+ return -EIO;
+
+ for (i = 0, n = 0; i < nvm->nregions; i++) {
+ u32 address, base, limit, region;
+ u8 id = nvm->regions[i].id;
+
+ address = NVM_FLREG(id);
+ region = idg_nvm_read32(nvm, address);
+
+ base = FIELD_GET(NVM_FREG_BASE_MASK, region) << NVM_FREG_ADDR_SHIFT;
+ limit = (FIELD_GET(NVM_FREG_ADDR_MASK, region) << NVM_FREG_ADDR_SHIFT) |
+ NVM_FREG_MIN_REGION_SIZE;
+
+ dev_dbg(device, "[%d] %s: region: 0x%08X base: 0x%08x limit: 0x%08x\n",
+ id, nvm->regions[i].name, region, base, limit);
+
+ if (base >= limit || (i > 0 && limit == 0)) {
+ dev_dbg(device, "[%d] %s: disabled\n",
+ id, nvm->regions[i].name);
+ nvm->regions[i].is_readable = 0;
+ continue;
+ }
+
+ if (nvm->size < limit)
+ nvm->size = limit;
+
+ nvm->regions[i].offset = base;
+ nvm->regions[i].size = limit - base + 1;
+ /* No write access to descriptor; mask it out*/
+ nvm->regions[i].is_writable = idg_nvm_region_writable(access_map, id);
+
+ nvm->regions[i].is_readable = idg_nvm_region_readable(access_map, id);
+ dev_dbg(device, "Registered, %s id=%d offset=%lld size=%lld rd=%d wr=%d\n",
+ nvm->regions[i].name,
+ nvm->regions[i].id,
+ nvm->regions[i].offset,
+ nvm->regions[i].size,
+ nvm->regions[i].is_readable,
+ nvm->regions[i].is_writable);
+
+ if (nvm->regions[i].is_readable)
+ n++;
+ }
+
+ nvm->non_posted_erase = non_posted_erase;
+
+ dev_dbg(device, "Registered %d regions\n", n);
+ dev_dbg(device, "Non posted erase %d\n", nvm->non_posted_erase);
+
+ /* Need to add 1 to the amount of memory
+ * so it is reported as an even block
+ */
+ nvm->size += 1;
+
+ return n;
+}
+
+static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ size_t total_len;
+ unsigned int idx;
+ ssize_t bytes;
+ loff_t from;
+ size_t len;
+ u8 region;
+ u64 addr;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ if (!IS_ALIGNED(info->addr, SZ_4K) || !IS_ALIGNED(info->len, SZ_4K)) {
+ dev_err(&mtd->dev, "unaligned erase %llx %llx\n",
+ info->addr, info->len);
+ info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+ return -EINVAL;
+ }
+
+ total_len = info->len;
+ addr = info->addr;
+
+ guard(mutex)(&nvm->lock);
+
+ while (total_len > 0) {
+ if (!IS_ALIGNED(addr, SZ_4K) || !IS_ALIGNED(total_len, SZ_4K)) {
+ dev_err(&mtd->dev, "unaligned erase %llx %zx\n", addr, total_len);
+ info->fail_addr = addr;
+ return -ERANGE;
+ }
+
+ idx = idg_nvm_get_region(nvm, addr);
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+ return -ERANGE;
+ }
+
+ from = addr - nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ len = total_len;
+ if (len > nvm->regions[idx].size - from)
+ len = nvm->regions[idx].size - from;
+
+ dev_dbg(&mtd->dev, "erasing region[%d] %s from %llx len %zx\n",
+ region, nvm->regions[idx].name, from, len);
+
+ bytes = idg_erase(nvm, region, from, len, &info->fail_addr);
+ if (bytes < 0) {
+ dev_dbg(&mtd->dev, "erase failed with %zd\n", bytes);
+ info->fail_addr += nvm->regions[idx].offset;
+ return bytes;
+ }
+
+ addr += len;
+ total_len -= len;
+ }
+
+ return 0;
+}
+
+static int intel_dg_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ unsigned int idx;
+ ssize_t ret;
+ u8 region;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ idx = idg_nvm_get_region(nvm, from);
+
+ dev_dbg(&mtd->dev, "reading region[%d] %s from %lld len %zd\n",
+ nvm->regions[idx].id, nvm->regions[idx].name, from, len);
+
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ return -ERANGE;
+ }
+
+ from -= nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ if (len > nvm->regions[idx].size - from)
+ len = nvm->regions[idx].size - from;
+
+ guard(mutex)(&nvm->lock);
+
+ ret = idg_read(nvm, region, from, len, buf);
+ if (ret < 0) {
+ dev_dbg(&mtd->dev, "read failed with %zd\n", ret);
+ return ret;
+ }
+
+ *retlen = ret;
+
+ return 0;
+}
+
+static int intel_dg_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ unsigned int idx;
+ ssize_t ret;
+ u8 region;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ idx = idg_nvm_get_region(nvm, to);
+
+ dev_dbg(&mtd->dev, "writing region[%d] %s to %lld len %zd\n",
+ nvm->regions[idx].id, nvm->regions[idx].name, to, len);
+
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ return -ERANGE;
+ }
+
+ to -= nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ if (len > nvm->regions[idx].size - to)
+ len = nvm->regions[idx].size - to;
+
+ guard(mutex)(&nvm->lock);
+
+ ret = idg_write(nvm, region, to, len, buf);
+ if (ret < 0) {
+ dev_dbg(&mtd->dev, "write failed with %zd\n", ret);
+ return ret;
+ }
+
+ *retlen = ret;
+
+ return 0;
+}
+
+static void intel_dg_nvm_release(struct kref *kref)
+{
+ struct intel_dg_nvm *nvm = container_of(kref, struct intel_dg_nvm, refcnt);
+ int i;
+
+ pr_debug("freeing intel_dg nvm\n");
+ for (i = 0; i < nvm->nregions; i++)
+ kfree(nvm->regions[i].name);
+ mutex_destroy(&nvm->lock);
+ kfree(nvm);
+}
+
+static int intel_dg_mtd_get_device(struct mtd_info *mtd)
+{
+ struct mtd_info *master = mtd_get_master(mtd);
+ struct intel_dg_nvm *nvm = master->priv;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+ pr_debug("get mtd %s %d\n", mtd->name, kref_read(&nvm->refcnt));
+ kref_get(&nvm->refcnt);
+
+ return 0;
+}
+
+static void intel_dg_mtd_put_device(struct mtd_info *mtd)
+{
+ struct mtd_info *master = mtd_get_master(mtd);
+ struct intel_dg_nvm *nvm = master->priv;
+
+ if (WARN_ON(!nvm))
+ return;
+ pr_debug("put mtd %s %d\n", mtd->name, kref_read(&nvm->refcnt));
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+}
+
+static int intel_dg_nvm_init_mtd(struct intel_dg_nvm *nvm, struct device *device,
+ unsigned int nparts, bool writable_override)
+{
+ struct mtd_partition *parts = NULL;
+ unsigned int i, n;
+ int ret;
+
+ dev_dbg(device, "registering with mtd\n");
+
+ nvm->mtd.owner = THIS_MODULE;
+ nvm->mtd.dev.parent = device;
+ nvm->mtd.flags = MTD_CAP_NORFLASH;
+ nvm->mtd.type = MTD_DATAFLASH;
+ nvm->mtd.priv = nvm;
+ nvm->mtd._write = intel_dg_mtd_write;
+ nvm->mtd._read = intel_dg_mtd_read;
+ nvm->mtd._erase = intel_dg_mtd_erase;
+ nvm->mtd._get_device = intel_dg_mtd_get_device;
+ nvm->mtd._put_device = intel_dg_mtd_put_device;
+ nvm->mtd.writesize = SZ_1; /* 1 byte granularity */
+ nvm->mtd.erasesize = SZ_4K; /* 4K bytes granularity */
+ nvm->mtd.size = nvm->size;
+
+ parts = kcalloc(nvm->nregions, sizeof(*parts), GFP_KERNEL);
+ if (!parts)
+ return -ENOMEM;
+
+ for (i = 0, n = 0; i < nvm->nregions && n < nparts; i++) {
+ if (!nvm->regions[i].is_readable)
+ continue;
+ parts[n].name = nvm->regions[i].name;
+ parts[n].offset = nvm->regions[i].offset;
+ parts[n].size = nvm->regions[i].size;
+ if (!nvm->regions[i].is_writable && !writable_override)
+ parts[n].mask_flags = MTD_WRITEABLE;
+ n++;
+ }
+
+ ret = mtd_device_register(&nvm->mtd, parts, n);
+
+ kfree(parts);
+ return ret;
+}
+
+static int intel_dg_mtd_probe(struct auxiliary_device *aux_dev,
+ const struct auxiliary_device_id *aux_dev_id)
+{
+ struct intel_dg_nvm_dev *invm = auxiliary_dev_to_intel_dg_nvm_dev(aux_dev);
+ struct intel_dg_nvm *nvm;
+ struct device *device;
+ unsigned int nregions;
+ unsigned int i, n;
+ int ret;
+
+ device = &aux_dev->dev;
+
+ /* count available regions */
+ for (nregions = 0, i = 0; i < INTEL_DG_NVM_REGIONS; i++) {
+ if (invm->regions[i].name)
+ nregions++;
+ }
+
+ if (!nregions) {
+ dev_err(device, "no regions defined\n");
+ return -ENODEV;
+ }
+
+ nvm = kzalloc(struct_size(nvm, regions, nregions), GFP_KERNEL);
+ if (!nvm)
+ return -ENOMEM;
+
+ kref_init(&nvm->refcnt);
+ mutex_init(&nvm->lock);
+
+ for (n = 0, i = 0; i < INTEL_DG_NVM_REGIONS; i++) {
+ if (!invm->regions[i].name)
+ continue;
+
+ char *name = kasprintf(GFP_KERNEL, "%s.%s",
+ dev_name(&aux_dev->dev), invm->regions[i].name);
+ if (!name)
+ continue;
+ nvm->regions[n].name = name;
+ nvm->regions[n].id = i;
+ n++;
+ }
+ nvm->nregions = n; /* in case where kasprintf fail */
+
+ nvm->base = devm_ioremap_resource(device, &invm->bar);
+ if (IS_ERR(nvm->base)) {
+ ret = PTR_ERR(nvm->base);
+ goto err;
+ }
+
+ if (invm->non_posted_erase) {
+ nvm->base2 = devm_ioremap_resource(device, &invm->bar2);
+ if (IS_ERR(nvm->base2)) {
+ ret = PTR_ERR(nvm->base2);
+ goto err;
+ }
+ }
+
+ ret = intel_dg_nvm_init(nvm, device, invm->non_posted_erase);
+ if (ret < 0) {
+ dev_err(device, "cannot initialize nvm %d\n", ret);
+ goto err;
+ }
+
+ ret = intel_dg_nvm_init_mtd(nvm, device, ret, invm->writable_override);
+ if (ret) {
+ dev_err(device, "failed init mtd %d\n", ret);
+ goto err;
+ }
+
+ dev_set_drvdata(&aux_dev->dev, nvm);
+
+ return 0;
+
+err:
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+ return ret;
+}
+
+static void intel_dg_mtd_remove(struct auxiliary_device *aux_dev)
+{
+ struct intel_dg_nvm *nvm = dev_get_drvdata(&aux_dev->dev);
+
+ if (!nvm)
+ return;
+
+ mtd_device_unregister(&nvm->mtd);
+
+ dev_set_drvdata(&aux_dev->dev, NULL);
+
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+}
+
+static const struct auxiliary_device_id intel_dg_mtd_id_table[] = {
+ {
+ .name = "i915.nvm",
+ },
+ {
+ .name = "xe.nvm",
+ },
+ {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(auxiliary, intel_dg_mtd_id_table);
+
+static struct auxiliary_driver intel_dg_mtd_driver = {
+ .probe = intel_dg_mtd_probe,
+ .remove = intel_dg_mtd_remove,
+ .driver = {
+ /* auxiliary_driver_register() sets .name to be the modname */
+ },
+ .id_table = intel_dg_mtd_id_table
+};
+module_auxiliary_driver(intel_dg_mtd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel DGFX MTD driver");
diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c
index 4dc4d65e7d32..8e604cc22ca3 100644
--- a/drivers/mtd/nand/qpic_common.c
+++ b/drivers/mtd/nand/qpic_common.c
@@ -57,14 +57,15 @@ qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc)
bam_txn_buf += sizeof(*bam_txn);
bam_txn->bam_ce = bam_txn_buf;
- bam_txn_buf +=
- sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS * num_cw;
+ bam_txn->bam_ce_nitems = QPIC_PER_CW_CMD_ELEMENTS * num_cw;
+ bam_txn_buf += sizeof(*bam_txn->bam_ce) * bam_txn->bam_ce_nitems;
bam_txn->cmd_sgl = bam_txn_buf;
- bam_txn_buf +=
- sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL * num_cw;
+ bam_txn->cmd_sgl_nitems = QPIC_PER_CW_CMD_SGL * num_cw;
+ bam_txn_buf += sizeof(*bam_txn->cmd_sgl) * bam_txn->cmd_sgl_nitems;
bam_txn->data_sgl = bam_txn_buf;
+ bam_txn->data_sgl_nitems = QPIC_PER_CW_DATA_SGL * num_cw;
init_completion(&bam_txn->txn_done);
@@ -238,6 +239,11 @@ int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
struct bam_transaction *bam_txn = nandc->bam_txn;
u32 offset;
+ if (bam_txn->bam_ce_pos + size > bam_txn->bam_ce_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "CE");
+ return -EINVAL;
+ }
+
bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_pos];
/* fill the command desc */
@@ -258,6 +264,12 @@ int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
/* use the separate sgl after this command */
if (flags & NAND_BAM_NEXT_SGL) {
+ if (bam_txn->cmd_sgl_pos >= bam_txn->cmd_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n",
+ "CMD sgl");
+ return -EINVAL;
+ }
+
bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_start];
bam_ce_size = (bam_txn->bam_ce_pos -
bam_txn->bam_ce_start) *
@@ -297,10 +309,20 @@ int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
struct bam_transaction *bam_txn = nandc->bam_txn;
if (read) {
+ if (bam_txn->rx_sgl_pos >= bam_txn->data_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "RX sgl");
+ return -EINVAL;
+ }
+
sg_set_buf(&bam_txn->data_sgl[bam_txn->rx_sgl_pos],
vaddr, size);
bam_txn->rx_sgl_pos++;
} else {
+ if (bam_txn->tx_sgl_pos >= bam_txn->data_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "TX sgl");
+ return -EINVAL;
+ }
+
sg_set_buf(&bam_txn->data_sgl[bam_txn->tx_sgl_pos],
vaddr, size);
bam_txn->tx_sgl_pos++;
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index 1003cf118c01..4dd6f1a4e797 100644
--- a/drivers/mtd/nand/raw/qcom_nandc.c
+++ b/drivers/mtd/nand/raw/qcom_nandc.c
@@ -1379,7 +1379,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
int cwperpage, bad_block_byte, ret;
bool wide_bus;
- int ecc_mode = 1;
+ int ecc_mode = ECC_MODE_8BIT;
/* controller only supports 512 bytes data steps */
ecc->size = NANDC_STEP_SIZE;
@@ -1400,7 +1400,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
if (ecc->strength >= 8) {
/* 8 bit ECC defaults to BCH ECC on all platforms */
host->bch_enabled = true;
- ecc_mode = 1;
+ ecc_mode = ECC_MODE_8BIT;
if (wide_bus) {
host->ecc_bytes_hw = 14;
@@ -1420,7 +1420,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
if (nandc->props->ecc_modes & ECC_BCH_4BIT) {
/* BCH */
host->bch_enabled = true;
- ecc_mode = 0;
+ ecc_mode = ECC_MODE_4BIT;
if (wide_bus) {
host->ecc_bytes_hw = 8;
diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c
index 4f12ff755df0..643513ee891b 100644
--- a/drivers/mtd/spi-nor/sysfs.c
+++ b/drivers/mtd/spi-nor/sysfs.c
@@ -104,7 +104,7 @@ static const struct attribute_group spi_nor_sysfs_group = {
.is_visible = spi_nor_sysfs_is_visible,
.is_bin_visible = spi_nor_sysfs_is_bin_visible,
.attrs = spi_nor_sysfs_entries,
- .bin_attrs_new = spi_nor_sysfs_bin_entries,
+ .bin_attrs = spi_nor_sysfs_bin_entries,
};
const struct attribute_group *spi_nor_sysfs_groups[] = {