Commit 74cae210 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'mtd/for-5.20' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux

Pull MTD updates from Richard Weinberger:
 "MTD core changes:

   - Dynamic partition support

   - Fix deadlock in sm_ftl

   - Various refcount fixes in maps, partitions and parser code

   - Integer overflow fixes in mtdchar

   - Support for Sercomm partitions

  NAND driver changes:

   - Clockrate fix for arasan

   - Add ATO25D1GA support

   - Double free fix for meson driver

   - Fix probe/remove methods in cafe NAND

   - Support unprotected spare data pages in qcom_nandc

  SPI NOR core changes:

   - move SECT_4K_PMC flag out of the core as it's a vendor specific
     flag

   - s/addr_width/addr_nbytes/g: address width means the number of IO
     lines used for the address, whereas in the code it is used as the
     number of address bytes.

   - do not change nor->addr_nbytes at SFDP parsing time. At the SFDP
     parsing time we should not change members of struct spi_nor, but
     instead fill members of struct spi_nor_flash_parameters which could
     later on be used by the callers.

   - track flash's internal address mode so that we can use 4B opcodes
     together with opcodes that don't have a 4B opcode correspondent.

  SPI NOR manufacturer drivers changes:

   - esmt: Rename "f25l32qa" flash name to "f25l32qa-2s".

   - micron-st: Skip FSR reading if SPI controller does not support it
     to allow flashes that support FSR to work even when attached to
     such SPI controllers.

   - spansion: Add s25hl-t/s25hs-t IDs and fixups"

* tag 'mtd/for-5.20' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (53 commits)
  mtd: core: check partition before dereference
  mtd: spi-nor: fix spi_nor_spimem_setup_op() call in spi_nor_erase_{sector,chip}()
  mtd: spi-nor: spansion: Add s25hl-t/s25hs-t IDs and fixups
  mtd: spi-nor: spansion: Add local function to discover page size
  mtd: spi-nor: core: Track flash's internal address mode
  mtd: spi-nor: core: Return error code from set_4byte_addr_mode()
  mtd: spi-nor: Do not change nor->addr_nbytes at SFDP parsing time
  mtd: spi-nor: core: Shrink the storage size of the flash_info's addr_nbytes
  mtd: spi-nor: s/addr_width/addr_nbytes
  mtd: spi-nor: esmt: Use correct name of f25l32qa
  mtd: spi-nor: micron-st: Skip FSR reading if SPI controller does not support it
  MAINTAINERS: Use my kernel.org email
  mtd: rawnand: arasan: Fix clock rate in NV-DDR
  mtd: rawnand: arasan: Update NAND bus clock instead of system clock
  mtd: core: introduce of support for dynamic partitions
  dt-bindings: mtd: partitions: add additional example for qcom,smem-part
  dt-bindings: mtd: partitions: support label/name only partition
  mtd: spi-nor: move SECT_4K_PMC special handling
  mtd: dataflash: Add SPI ID table
  mtd: hyperbus: rpc-if: Fix RPM imbalance in probe error path
  ...
parents 79b7e67b 7ec4cdb3
......@@ -37,6 +37,4 @@ examples:
compatible = "fsl,imx27-nand";
reg = <0xd8000000 0x1000>;
interrupts = <29>;
nand-bus-width = <8>;
nand-ecc-mode = "hw";
};
......@@ -11,6 +11,17 @@ description: |
relative offset and size specified. Depending on partition function extra
properties can be used.
A partition may be dynamically allocated by a specific parser at runtime.
In this specific case, a specific suffix is required to the node name.
Everything after 'partition-' will be used as the partition name to compare
with the one dynamically allocated by the specific parser.
If the partition contains invalid char a label can be provided that will
be used instead of the node name to make the comparison.
This is used to assign an OF node to the dynamiccally allocated partition
so that subsystem like NVMEM can provide an OF node and declare NVMEM cells.
The OF node will be assigned only if the partition label declared match the
one assigned by the parser at runtime.
maintainers:
- Rafał Miłecki <rafal@milecki.pl>
......@@ -41,7 +52,12 @@ properties:
immune to paired-pages corruptions
type: boolean
required:
- reg
if:
not:
required: [ reg ]
then:
properties:
$nodename:
pattern: '^partition-.*$'
additionalProperties: true
......@@ -19,6 +19,10 @@ properties:
compatible:
const: qcom,smem-part
patternProperties:
"^partition-[0-9a-z]+$":
$ref: partition.yaml#
required:
- compatible
......@@ -31,3 +35,26 @@ examples:
compatible = "qcom,smem-part";
};
};
- |
/* Example declaring dynamic partition */
flash {
partitions {
compatible = "qcom,smem-part";
partition-art {
compatible = "nvmem-cells";
#address-cells = <1>;
#size-cells = <1>;
label = "0:art";
macaddr_art_0: macaddr@0 {
reg = <0x0 0x6>;
};
macaddr_art_6: macaddr@6 {
reg = <0x6 0x6>;
};
};
};
};
......@@ -102,6 +102,31 @@ allOf:
- const: rx
- const: cmd
- if:
properties:
compatible:
contains:
enum:
- qcom,ipq806x-nand
then:
properties:
qcom,boot-partitions:
$ref: /schemas/types.yaml#/definitions/uint32-matrix
items:
items:
- description: offset
- description: size
description:
Boot partition use a different layout where the 4 bytes of spare
data are not protected by ECC. Use this to declare these special
partitions by defining first the offset and then the size.
It's in the form of <offset1 size1 offset2 size2 offset3 ...>
and should be declared in ascending order.
Refer to the ipq8064 example on how to use this special binding.
required:
- compatible
- reg
......@@ -135,6 +160,8 @@ examples:
nand-ecc-strength = <4>;
nand-bus-width = <8>;
qcom,boot-partitions = <0x0 0x58a0000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
......
......@@ -19130,7 +19130,7 @@ F: drivers/pinctrl/spear/
SPI NOR SUBSYSTEM
M: Tudor Ambarus <tudor.ambarus@microchip.com>
M: Pratyush Yadav <p.yadav@ti.com>
M: Pratyush Yadav <pratyush@kernel.org>
R: Michael Walle <michael@walle.cc>
L: linux-mtd@lists.infradead.org
S: Maintained
......
......@@ -112,6 +112,13 @@ static const struct of_device_id dataflash_dt_ids[] = {
MODULE_DEVICE_TABLE(of, dataflash_dt_ids);
#endif
static const struct spi_device_id dataflash_spi_ids[] = {
{ .name = "at45", },
{ .name = "dataflash", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, dataflash_spi_ids);
/* ......................................................................... */
/*
......@@ -936,6 +943,7 @@ static struct spi_driver dataflash_driver = {
.probe = dataflash_probe,
.remove = dataflash_remove,
.id_table = dataflash_spi_ids,
/* FIXME: investigate suspend and resume... */
};
......
......@@ -270,7 +270,9 @@ static int powernv_flash_release(struct platform_device *pdev)
struct powernv_flash *data = dev_get_drvdata(&(pdev->dev));
/* All resources should be freed automatically */
return mtd_device_unregister(&(data->mtd));
WARN_ON(mtd_device_unregister(&data->mtd));
return 0;
}
static const struct of_device_id powernv_flash_match[] = {
......
......@@ -1045,13 +1045,9 @@ static int spear_smi_remove(struct platform_device *pdev)
{
struct spear_smi *dev;
struct spear_snor_flash *flash;
int ret, i;
int i;
dev = platform_get_drvdata(pdev);
if (!dev) {
dev_err(&pdev->dev, "dev is null\n");
return -ENODEV;
}
/* clean up for all nor flash */
for (i = 0; i < dev->num_flashes; i++) {
......@@ -1060,9 +1056,7 @@ static int spear_smi_remove(struct platform_device *pdev)
continue;
/* clean up mtd stuff */
ret = mtd_device_unregister(&flash->mtd);
if (ret)
dev_err(&pdev->dev, "error removing mtd\n");
WARN_ON(mtd_device_unregister(&flash->mtd));
}
clk_disable_unprepare(dev->clk);
......
......@@ -2084,15 +2084,12 @@ static int stfsm_probe(struct platform_device *pdev)
* Configure READ/WRITE/ERASE sequences according to platform and
* device flags.
*/
if (info->config) {
if (info->config)
ret = info->config(fsm);
if (ret)
goto err_clk_unprepare;
} else {
else
ret = stfsm_prepare_rwe_seqs_default(fsm);
if (ret)
goto err_clk_unprepare;
}
if (ret)
goto err_clk_unprepare;
fsm->mtd.name = info->name;
fsm->mtd.dev.parent = &pdev->dev;
......@@ -2115,10 +2112,12 @@ static int stfsm_probe(struct platform_device *pdev)
(long long)fsm->mtd.size, (long long)(fsm->mtd.size >> 20),
fsm->mtd.erasesize, (fsm->mtd.erasesize >> 10));
return mtd_device_register(&fsm->mtd, NULL, 0);
ret = mtd_device_register(&fsm->mtd, NULL, 0);
if (ret) {
err_clk_unprepare:
clk_disable_unprepare(fsm->clk);
clk_disable_unprepare(fsm->clk);
}
return ret;
}
......@@ -2126,9 +2125,11 @@ static int stfsm_remove(struct platform_device *pdev)
{
struct stfsm *fsm = platform_get_drvdata(pdev);
WARN_ON(mtd_device_unregister(&fsm->mtd));
clk_disable_unprepare(fsm->clk);
return mtd_device_unregister(&fsm->mtd);
return 0;
}
#ifdef CONFIG_PM_SLEEP
......
......@@ -233,16 +233,16 @@ static int am654_hbmc_remove(struct platform_device *pdev)
{
struct am654_hbmc_priv *priv = platform_get_drvdata(pdev);
struct am654_hbmc_device_priv *dev_priv = priv->hbdev.priv;
int ret;
ret = hyperbus_unregister_device(&priv->hbdev);
hyperbus_unregister_device(&priv->hbdev);
if (priv->mux_ctrl)
mux_control_deselect(priv->mux_ctrl);
if (dev_priv->rx_chan)
dma_release_channel(dev_priv->rx_chan);
return ret;
return 0;
}
static const struct of_device_id am654_hbmc_dt_ids[] = {
......
......@@ -126,16 +126,12 @@ int hyperbus_register_device(struct hyperbus_device *hbdev)
}
EXPORT_SYMBOL_GPL(hyperbus_register_device);
int hyperbus_unregister_device(struct hyperbus_device *hbdev)
void hyperbus_unregister_device(struct hyperbus_device *hbdev)
{
int ret = 0;
if (hbdev && hbdev->mtd) {
ret = mtd_device_unregister(hbdev->mtd);
WARN_ON(mtd_device_unregister(hbdev->mtd));
map_destroy(hbdev->mtd);
}
return ret;
}
EXPORT_SYMBOL_GPL(hyperbus_unregister_device);
......
......@@ -134,7 +134,7 @@ static int rpcif_hb_probe(struct platform_device *pdev)
error = rpcif_hw_init(&hyperbus->rpc, true);
if (error)
return error;
goto out_disable_rpm;
hyperbus->hbdev.map.size = hyperbus->rpc.size;
hyperbus->hbdev.map.virt = hyperbus->rpc.dirmap;
......@@ -145,19 +145,24 @@ static int rpcif_hb_probe(struct platform_device *pdev)
hyperbus->hbdev.np = of_get_next_child(pdev->dev.parent->of_node, NULL);
error = hyperbus_register_device(&hyperbus->hbdev);
if (error)
rpcif_disable_rpm(&hyperbus->rpc);
goto out_disable_rpm;
return 0;
out_disable_rpm:
rpcif_disable_rpm(&hyperbus->rpc);
return error;
}
static int rpcif_hb_remove(struct platform_device *pdev)
{
struct rpcif_hyperbus *hyperbus = platform_get_drvdata(pdev);
int error = hyperbus_unregister_device(&hyperbus->hbdev);
hyperbus_unregister_device(&hyperbus->hbdev);
rpcif_disable_rpm(&hyperbus->rpc);
return error;
return 0;
}
static struct platform_driver rpcif_platform_driver = {
......
......@@ -478,7 +478,9 @@ static int lpddr2_nvm_probe(struct platform_device *pdev)
*/
static int lpddr2_nvm_remove(struct platform_device *pdev)
{
return mtd_device_unregister(dev_get_drvdata(&pdev->dev));
WARN_ON(mtd_device_unregister(dev_get_drvdata(&pdev->dev)));
return 0;
}
/* Initialize platform_driver data structure for lpddr2_nvm */
......
......@@ -66,18 +66,12 @@ static int physmap_flash_remove(struct platform_device *dev)
{
struct physmap_flash_info *info;
struct physmap_flash_data *physmap_data;
int i, err = 0;
int i;
info = platform_get_drvdata(dev);
if (!info) {
err = -EINVAL;
goto out;
}
if (info->cmtd) {
err = mtd_device_unregister(info->cmtd);
if (err)
goto out;
WARN_ON(mtd_device_unregister(info->cmtd));
if (info->cmtd != info->mtds[0])
mtd_concat_destroy(info->cmtd);
......@@ -92,10 +86,9 @@ static int physmap_flash_remove(struct platform_device *dev)
if (physmap_data && physmap_data->exit)
physmap_data->exit(dev);
out:
pm_runtime_put(&dev->dev);
pm_runtime_disable(&dev->dev);
return err;
return 0;
}
static void physmap_set_vpp(struct map_info *map, int state)
......
......@@ -93,6 +93,7 @@ static int ap_flash_init(struct platform_device *pdev)
return -ENODEV;
}
ebi_base = of_iomap(ebi, 0);
of_node_put(ebi);
if (!ebi_base)
return -ENODEV;
......@@ -207,6 +208,7 @@ int of_flash_probe_versatile(struct platform_device *pdev,
versatile_flashprot = (enum versatile_flashprot)devid->data;
rmap = syscon_node_to_regmap(sysnp);
of_node_put(sysnp);
if (IS_ERR(rmap))
return PTR_ERR(rmap);
......
......@@ -615,21 +615,24 @@ static int mtdchar_write_ioctl(struct mtd_info *mtd,
if (!usr_oob)
req.ooblen = 0;
req.len &= 0xffffffff;
req.ooblen &= 0xffffffff;
if (req.start + req.len > mtd->size)
return -EINVAL;
datbuf_len = min_t(size_t, req.len, mtd->erasesize);
if (datbuf_len > 0) {
datbuf = kmalloc(datbuf_len, GFP_KERNEL);
datbuf = kvmalloc(datbuf_len, GFP_KERNEL);
if (!datbuf)
return -ENOMEM;
}
oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize);
if (oobbuf_len > 0) {
oobbuf = kmalloc(oobbuf_len, GFP_KERNEL);
oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL);
if (!oobbuf) {
kfree(datbuf);
kvfree(datbuf);
return -ENOMEM;
}
}
......@@ -679,8 +682,8 @@ static int mtdchar_write_ioctl(struct mtd_info *mtd,
usr_oob += ops.oobretlen;
}
kfree(datbuf);
kfree(oobbuf);
kvfree(datbuf);
kvfree(oobbuf);
return ret;
}
......
......@@ -546,6 +546,68 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
return 0;
}
static void mtd_check_of_node(struct mtd_info *mtd)
{
struct device_node *partitions, *parent_dn, *mtd_dn = NULL;
const char *pname, *prefix = "partition-";
int plen, mtd_name_len, offset, prefix_len;
struct mtd_info *parent;
bool found = false;
/* Check if MTD already has a device node */
if (dev_of_node(&mtd->dev))
return;
/* Check if a partitions node exist */
if (!mtd_is_partition(mtd))
return;
parent = mtd->parent;
parent_dn = dev_of_node(&parent->dev);
if (!parent_dn)
return;
partitions = of_get_child_by_name(parent_dn, "partitions");
if (!partitions)
goto exit_parent;
prefix_len = strlen(prefix);
mtd_name_len = strlen(mtd->name);
/* Search if a partition is defined with the same name */
for_each_child_of_node(partitions, mtd_dn) {
offset = 0;
/* Skip partition with no/wrong prefix */
if (!of_node_name_prefix(mtd_dn, "partition-"))
continue;
/* Label have priority. Check that first */
if (of_property_read_string(mtd_dn, "label", &pname)) {
of_property_read_string(mtd_dn, "name", &pname);
offset = prefix_len;
}
plen = strlen(pname) - offset;
if (plen == mtd_name_len &&
!strncmp(mtd->name, pname + offset, plen)) {
found = true;
break;
}
}
if (!found)
goto exit_partitions;
/* Set of_node only for nvmem */
if (of_device_is_compatible(mtd_dn, "nvmem-cells"))
mtd_set_of_node(mtd, mtd_dn);
exit_partitions:
of_node_put(partitions);
exit_parent:
of_node_put(parent_dn);
}
/**
* add_mtd_device - register an MTD device
* @mtd: pointer to new MTD device info structure
......@@ -658,6 +720,7 @@ int add_mtd_device(struct mtd_info *mtd)
mtd->dev.devt = MTD_DEVT(i);
dev_set_name(&mtd->dev, "mtd%d", i);
dev_set_drvdata(&mtd->dev, mtd);
mtd_check_of_node(mtd);
of_node_get(mtd_get_of_node(mtd));
error = device_register(&mtd->dev);
if (error)
......
......@@ -347,17 +347,17 @@ static int anfc_select_target(struct nand_chip *chip, int target)
/* Update clock frequency */
if (nfc->cur_clk != anand->clk) {
clk_disable_unprepare(nfc->controller_clk);
ret = clk_set_rate(nfc->controller_clk, anand->clk);
clk_disable_unprepare(nfc->bus_clk);
ret = clk_set_rate(nfc->bus_clk, anand->clk);
if (ret) {
dev_err(nfc->dev, "Failed to change clock rate\n");
return ret;
}
ret = clk_prepare_enable(nfc->controller_clk);
ret = clk_prepare_enable(nfc->bus_clk);
if (ret) {
dev_err(nfc->dev,
"Failed to re-enable the controller clock\n");
"Failed to re-enable the bus clock\n");
return ret;
}
......@@ -1043,7 +1043,13 @@ static int anfc_setup_interface(struct nand_chip *chip, int target,
DQS_BUFF_SEL_OUT(dqs_mode);
}
anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK;
if (nand_interface_is_sdr(conf)) {
anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK;
} else {
/* ONFI timings are defined in picoseconds */
anand->clk = div_u64((u64)NSEC_PER_SEC * 1000,
conf->timings.nvddr.tCK_min);
}
/*
* Due to a hardware bug in the ZynqMP SoC, SDR timing modes 0-1 work
......
......@@ -2629,7 +2629,9 @@ static int atmel_nand_controller_remove(struct platform_device *pdev)
{
struct atmel_nand_controller *nc = platform_get_drvdata(pdev);
return nc->caps->ops->remove(nc);
WARN_ON(nc->caps->ops->remove(nc));
return 0;
}
static __maybe_unused int atmel_nand_controller_resume(struct device *dev)
......
......@@ -679,8 +679,10 @@ static int cafe_nand_probe(struct pci_dev *pdev,
pci_set_master(pdev);
cafe = kzalloc(sizeof(*cafe), GFP_KERNEL);
if (!cafe)
return -ENOMEM;
if (!cafe) {
err = -ENOMEM;
goto out_disable_device;
}
mtd = nand_to_mtd(&cafe->nand);
mtd->dev.parent = &pdev->dev;
......@@ -801,6 +803,8 @@ static int cafe_nand_probe(struct pci_dev *pdev,
pci_iounmap(pdev, cafe->mmio);
out_free_mtd:
kfree(cafe);
out_disable_device:
pci_disable_device(pdev);
out:
return err;
}
......@@ -822,6 +826,7 @@ static void cafe_nand_remove(struct pci_dev *pdev)
pci_iounmap(pdev, cafe->mmio);
dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr);
kfree(cafe);
pci_disable_device(pdev);
}
static const struct pci_device_id cafe_nand_tbl[] = {
......
......@@ -1293,26 +1293,20 @@ meson_nfc_nand_chip_init(struct device *dev,
return 0;
}
static int meson_nfc_nand_chip_cleanup(struct meson_nfc *nfc)
static void meson_nfc_nand_chip_cleanup(struct meson_nfc *nfc)
{
struct meson_nfc_nand_chip *meson_chip;
struct mtd_info *mtd;
int ret;
while (!list_empty(&nfc->chips)) {
meson_chip = list_first_entry(&nfc->chips,
struct meson_nfc_nand_chip, node);
mtd = nand_to_mtd(&meson_chip->nand);
ret = mtd_device_unregister(mtd);
if (ret)
return ret;
WARN_ON(mtd_device_unregister(mtd));
meson_nfc_free_buffer(&meson_chip->nand);
nand_cleanup(&meson_chip->nand);
list_del(&meson_chip->node);
}
return 0;
}
static int meson_nfc_nand_chips_init(struct device *dev,
......@@ -1445,16 +1439,11 @@ static int meson_nfc_probe(struct platform_device *pdev)
static int meson_nfc_remove(struct platform_device *pdev)
{
struct meson_nfc *nfc = platform_get_drvdata(pdev);
int ret;
ret = meson_nfc_nand_chip_cleanup(nfc);
if (ret)
return ret;
meson_nfc_nand_chip_cleanup(nfc);
meson_nfc_disable_clk(nfc);
platform_set_drvdata(pdev, NULL);
return 0;
}
......
......@@ -2278,16 +2278,14 @@ static int omap_nand_remove(struct platform_device *pdev)
struct mtd_info *mtd = platform_get_drvdata(pdev);
struct nand_chip *nand_chip = mtd_to_nand(mtd);
struct omap_nand_info *info = mtd_to_omap(mtd);
int ret;
rawnand_sw_bch_cleanup(nand_chip);
if (info->dma)
dma_release_channel(info->dma);
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
WARN_ON(mtd_device_unregister(mtd));
nand_cleanup(nand_chip);
return ret;
return 0;
}
/* omap_nand_ids defined in linux/platform_data/mtd-nand-omap2.h */
......
This diff is collapsed.
......@@ -52,7 +52,7 @@ static const struct mtd_ooblayout_ops oob_sm_ops = {
.free = oob_sm_ooblayout_free,
};
/* NOTE: This layout is is not compatabable with SmartMedia, */
/* NOTE: This layout is not compatabable with SmartMedia, */
/* because the 256 byte devices have page depenent oob layout */
/* However it does preserve the bad block markers */
/* If you use smftl, it will bypass this and work correctly */
......
......@@ -1223,11 +1223,8 @@ static int tegra_nand_remove(struct platform_device *pdev)
struct tegra_nand_controller *ctrl = platform_get_drvdata(pdev);
struct nand_chip *chip = ctrl->chip;
struct mtd_info *mtd = nand_to_mtd(chip);
int ret;
ret = mtd_device_unregister(mtd);
if (ret)
return ret;
WARN_ON(mtd_device_unregister(mtd));
nand_cleanup(chip);
......
# SPDX-License-Identifier: GPL-2.0
spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
spinand-objs := core.o ato.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Aidan MacDonald
*
* Author: Aidan MacDonald <aidanmacdonald.0x0@gmail.com>
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define SPINAND_MFR_ATO 0x9b
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
SPINAND_PROG_LOAD(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
SPINAND_PROG_LOAD(false, 0, NULL, 0));
static int ato25d1ga_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = (16 * section) + 8;
region->length = 8;
return 0;
}
static int ato25d1ga_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
if (section) {
region->offset = (16 * section);
region->length = 8;
} else {
/* first byte of section 0 is reserved for the BBM */
region->offset = 1;
region->length = 7;
}
return 0;
}
static const struct mtd_ooblayout_ops ato25d1ga_ooblayout = {
.ecc = ato25d1ga_ooblayout_ecc,
.free = ato25d1ga_ooblayout_free,
};
static const struct spinand_info ato_spinand_table[] = {
SPINAND_INFO("ATO25D1GA",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x12),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&ato25d1ga_ooblayout, NULL)),
};
static const struct spinand_manufacturer_ops ato_spinand_manuf_ops = {
};
const struct spinand_manufacturer ato_spinand_manufacturer = {
.id = SPINAND_MFR_ATO,
.name = "ATO",
.chips = ato_spinand_table,
.nchips = ARRAY_SIZE(ato_spinand_table),
.ops = &ato_spinand_manuf_ops,
};
......@@ -927,6 +927,7 @@ static const struct nand_ops spinand_ops = {
};
static const struct spinand_manufacturer *spinand_manufacturers[] = {
&ato_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
&macronix_spinand_manufacturer,
&micron_spinand_manufacturer,
......
......@@ -186,3 +186,12 @@ config MTD_QCOMSMEM_PARTS
help
This provides support for parsing partitions from Shared Memory (SMEM)
for NAND and SPI flash on Qualcomm platforms.
config MTD_SERCOMM_PARTS
tristate "Sercomm partition table parser"
depends on MTD && RALINK
help
This provides partitions table parser for devices with Sercomm
partition map. This partition table contains real partition
offsets, which may differ from device to device depending on the
number and location of bad blocks on NAND.
......@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o
obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o
obj-$(CONFIG_MTD_SERCOMM_PARTS) += scpart.o
obj-$(CONFIG_MTD_SHARPSL_PARTS) += sharpslpart.o
obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
obj-$(CONFIG_MTD_QCOMSMEM_PARTS) += qcomsmempart.o
......@@ -35,12 +35,15 @@ static long long bcm4908_partitions_fw_offset(void)
err = kstrtoul(s + len + 1, 0, &offset);
if (err) {
pr_err("failed to parse %s\n", s + len + 1);
of_node_put(root);
return err;
}
of_node_put(root);
return offset << 10;
}
of_node_put(root);
return -ENOENT;
}
......
......@@ -58,6 +58,7 @@ static void parse_redboot_of(struct mtd_info *master)
return;
ret = of_property_read_u32(npart, "fis-index-block", &dirblock);
of_node_put(npart);
if (ret)
return;
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/mtd/scpart.c: Sercomm Partition Parser
*
* Copyright (C) 2018 NOGUCHI Hiroshi
* Copyright (C) 2022 Mikhail Zhilkin
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/module.h>
#define MOD_NAME "scpart"
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) MOD_NAME ": " fmt
#define ID_ALREADY_FOUND 0xffffffffUL
#define MAP_OFFS_IN_BLK 0x800
#define MAP_MIRROR_NUM 2
static const char sc_part_magic[] = {
'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
};
#define PART_MAGIC_LEN sizeof(sc_part_magic)
/* assumes that all fields are set by CPU native endian */
struct sc_part_desc {
uint32_t part_id;
uint32_t part_offs;
uint32_t part_bytes;
};
static uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
{
return ((pdesc->part_id != 0xffffffffUL) &&
(pdesc->part_offs != 0xffffffffUL) &&
(pdesc->part_bytes != 0xffffffffUL));
}
static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
struct sc_part_desc **ppdesc)
{
int cnt = 0;
int res = 0;
int res2;
loff_t offs;
size_t retlen;
struct sc_part_desc *pdesc = NULL;
struct sc_part_desc *tmpdesc;
uint8_t *buf;
buf = kzalloc(master->erasesize, GFP_KERNEL);
if (!buf) {
res = -ENOMEM;
goto out;
}
res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
if (res2 || retlen != master->erasesize) {
res = -EIO;
goto free;
}
for (offs = MAP_OFFS_IN_BLK;
offs < master->erasesize - sizeof(*tmpdesc);
offs += sizeof(*tmpdesc)) {
tmpdesc = (struct sc_part_desc *)&buf[offs];
if (!scpart_desc_is_valid(tmpdesc))
break;
cnt++;
}
if (cnt > 0) {
int bytes = cnt * sizeof(*pdesc);
pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
if (!pdesc) {
res = -ENOMEM;
goto free;
}
memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
*ppdesc = pdesc;
res = cnt;
}
free:
kfree(buf);
out:
return res;
}
static int scpart_find_partmap(struct mtd_info *master,
struct sc_part_desc **ppdesc)
{
int magic_found = 0;
int res = 0;
int res2;
loff_t offs = 0;
size_t retlen;
uint8_t rdbuf[PART_MAGIC_LEN];
while ((magic_found < MAP_MIRROR_NUM) &&
(offs < master->size) &&
!mtd_block_isbad(master, offs)) {
res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
if (res2 || retlen != PART_MAGIC_LEN) {
res = -EIO;
goto out;
}
if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
pr_debug("Signature found at 0x%llx\n", offs);
magic_found++;
res = scpart_scan_partmap(master, offs, ppdesc);
if (res > 0)
goto out;
}
offs += master->erasesize;
}
out:
if (res > 0)
pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
else
pr_info("No valid 'SC PART MAP' was found\n");
return res;
}
static int scpart_parse(struct mtd_info *master,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
const char *partname;
int n;
int nr_scparts;
int nr_parts = 0;
int res = 0;
struct sc_part_desc *scpart_map = NULL;
struct mtd_partition *parts = NULL;
struct device_node *mtd_node;
struct device_node *ofpart_node;
struct device_node *pp;
mtd_node = mtd_get_of_node(master);
if (!mtd_node) {
res = -ENOENT;
goto out;
}
ofpart_node = of_get_child_by_name(mtd_node, "partitions");
if (!ofpart_node) {
pr_info("%s: 'partitions' subnode not found on %pOF.\n",
master->name, mtd_node);
res = -ENOENT;
goto out;
}
nr_scparts = scpart_find_partmap(master, &scpart_map);
if (nr_scparts <= 0) {
pr_info("No any partitions was found in 'SC PART MAP'.\n");
res = -ENOENT;
goto free;
}
parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
GFP_KERNEL);
if (!parts) {
res = -ENOMEM;
goto free;
}
for_each_child_of_node(ofpart_node, pp) {
u32 scpart_id;
if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
continue;
for (n = 0 ; n < nr_scparts ; n++)
if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
(scpart_id == scpart_map[n].part_id))
break;
if (n >= nr_scparts)
/* not match */
continue;
/* add the partition found in OF into MTD partition array */
parts[nr_parts].offset = scpart_map[n].part_offs;
parts[nr_parts].size = scpart_map[n].part_bytes;
parts[nr_parts].of_node = pp;
if (!of_property_read_string(pp, "label", &partname))
parts[nr_parts].name = partname;
if (of_property_read_bool(pp, "read-only"))
parts[nr_parts].mask_flags |= MTD_WRITEABLE;
if (of_property_read_bool(pp, "lock"))
parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
/* mark as 'done' */
scpart_map[n].part_id = ID_ALREADY_FOUND;
nr_parts++;
}
if (nr_parts > 0) {
*pparts = parts;
res = nr_parts;
} else
pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
of_node_put(pp);
free:
of_node_put(ofpart_node);
kfree(scpart_map);
if (res <= 0)
kfree(parts);
out:
return res;
}
static const struct of_device_id scpart_parser_of_match_table[] = {
{ .compatible = "sercomm,sc-partitions" },
{},
};
MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
static struct mtd_part_parser scpart_parser = {
.parse_fn = scpart_parse,
.name = "scpart",
.of_match_table = scpart_parser_of_match_table,
};
module_mtd_part_parser(scpart_parser);
/* mtd parsers will request the module by parser name */
MODULE_ALIAS("scpart");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
MODULE_DESCRIPTION("Sercomm partition parser");
......@@ -1111,9 +1111,9 @@ static void sm_release(struct mtd_blktrans_dev *dev)
{
struct sm_ftl *ftl = dev->priv;
mutex_lock(&ftl->mutex);
del_timer_sync(&ftl->timer);
cancel_work_sync(&ftl->flush_work);
mutex_lock(&ftl->mutex);
sm_cache_flush(ftl);
mutex_unlock(&ftl->mutex);
}
......
......@@ -237,7 +237,7 @@ static int hisi_spi_nor_dma_transfer(struct spi_nor *nor, loff_t start_off,
reg = readl(host->regbase + FMC_CFG);
reg &= ~(FMC_CFG_OP_MODE_MASK | SPI_NOR_ADDR_MODE_MASK);
reg |= FMC_CFG_OP_MODE_NORMAL;
reg |= (nor->addr_width == 4) ? SPI_NOR_ADDR_MODE_4BYTES
reg |= (nor->addr_nbytes == 4) ? SPI_NOR_ADDR_MODE_4BYTES
: SPI_NOR_ADDR_MODE_3BYTES;
writel(reg, host->regbase + FMC_CFG);
......
......@@ -203,7 +203,7 @@ static ssize_t nxp_spifi_write(struct spi_nor *nor, loff_t to, size_t len,
SPIFI_CMD_DATALEN(len) |
SPIFI_CMD_FIELDFORM_ALL_SERIAL |
SPIFI_CMD_OPCODE(nor->program_opcode) |
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_width + 1);
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_nbytes + 1);
writel(cmd, spifi->io_base + SPIFI_CMD);
for (i = 0; i < len; i++)
......@@ -230,7 +230,7 @@ static int nxp_spifi_erase(struct spi_nor *nor, loff_t offs)
cmd = SPIFI_CMD_FIELDFORM_ALL_SERIAL |
SPIFI_CMD_OPCODE(nor->erase_opcode) |
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_width + 1);
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_nbytes + 1);
writel(cmd, spifi->io_base + SPIFI_CMD);
return nxp_spifi_wait_for_cmd(spifi);
......@@ -252,12 +252,12 @@ static int nxp_spifi_setup_memory_cmd(struct nxp_spifi *spifi)
}
/* Memory mode supports address length between 1 and 4 */
if (spifi->nor.addr_width < 1 || spifi->nor.addr_width > 4)
if (spifi->nor.addr_nbytes < 1 || spifi->nor.addr_nbytes > 4)
return -EINVAL;
spifi->mcmd |= SPIFI_CMD_OPCODE(spifi->nor.read_opcode) |
SPIFI_CMD_INTLEN(spifi->nor.read_dummy / 8) |
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_width + 1);
SPIFI_CMD_FRAMEFORM(spifi->nor.addr_nbytes + 1);
return 0;
}
......
......@@ -38,7 +38,7 @@
*/
#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES (40UL * HZ)
#define SPI_NOR_MAX_ADDR_WIDTH 4
#define SPI_NOR_MAX_ADDR_NBYTES 4
#define SPI_NOR_SRST_SLEEP_MIN 200
#define SPI_NOR_SRST_SLEEP_MAX 400
......@@ -177,7 +177,7 @@ int spi_nor_controller_ops_write_reg(struct spi_nor *nor, u8 opcode,
static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs)
{
if (spi_nor_protocol_is_dtr(nor->write_proto))
if (spi_nor_protocol_is_dtr(nor->reg_proto))
return -EOPNOTSUPP;
return nor->controller_ops->erase(nor, offs);
......@@ -198,7 +198,7 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
SPI_MEM_OP_ADDR(nor->addr_width, from, 0),
SPI_MEM_OP_ADDR(nor->addr_nbytes, from, 0),
SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
SPI_MEM_OP_DATA_IN(len, buf, 0));
bool usebouncebuf;
......@@ -262,7 +262,7 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0),
SPI_MEM_OP_ADDR(nor->addr_width, to, 0),
SPI_MEM_OP_ADDR(nor->addr_nbytes, to, 0),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(len, buf, 0));
ssize_t nbytes;
......@@ -972,7 +972,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor)
if (nor->spimem) {
struct spi_mem_op op = SPI_NOR_CHIP_ERASE_OP;
spi_nor_spimem_setup_op(nor, &op, nor->write_proto);
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
ret = spi_mem_exec_op(nor->spimem, &op);
} else {
......@@ -1113,9 +1113,9 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
if (nor->spimem) {
struct spi_mem_op op =
SPI_NOR_SECTOR_ERASE_OP(nor->erase_opcode,
nor->addr_width, addr);
nor->addr_nbytes, addr);
spi_nor_spimem_setup_op(nor, &op, nor->write_proto);
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
return spi_mem_exec_op(nor->spimem, &op);
} else if (nor->controller_ops->erase) {
......@@ -1126,13 +1126,13 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
* Default implementation, if driver doesn't have a specialized HW
* control
*/
for (i = nor->addr_width - 1; i >= 0; i--) {
for (i = nor->addr_nbytes - 1; i >= 0; i--) {
nor->bouncebuf[i] = addr & 0xff;
addr >>= 8;
}
return spi_nor_controller_ops_write_reg(nor, nor->erase_opcode,
nor->bouncebuf, nor->addr_width);
nor->bouncebuf, nor->addr_nbytes);
}
/**
......@@ -2249,43 +2249,43 @@ static int spi_nor_default_setup(struct spi_nor *nor,
return 0;
}
static int spi_nor_set_addr_width(struct spi_nor *nor)
static int spi_nor_set_addr_nbytes(struct spi_nor *nor)
{
if (nor->addr_width) {
/* already configured from SFDP */
if (nor->params->addr_nbytes) {
nor->addr_nbytes = nor->params->addr_nbytes;
} else if (nor->read_proto == SNOR_PROTO_8_8_8_DTR) {
/*
* In 8D-8D-8D mode, one byte takes half a cycle to transfer. So
* in this protocol an odd address width cannot be used because
* in this protocol an odd addr_nbytes cannot be used because
* then the address phase would only span a cycle and a half.
* Half a cycle would be left over. We would then have to start
* the dummy phase in the middle of a cycle and so too the data
* phase, and we will end the transaction with half a cycle left
* over.
*
* Force all 8D-8D-8D flashes to use an address width of 4 to
* Force all 8D-8D-8D flashes to use an addr_nbytes of 4 to
* avoid this situation.
*/
nor->addr_width = 4;
} else if (nor->info->addr_width) {
nor->addr_width = nor->info->addr_width;
nor->addr_nbytes = 4;
} else if (nor->info->addr_nbytes) {
nor->addr_nbytes = nor->info->addr_nbytes;
} else {
nor->addr_width = 3;
nor->addr_nbytes = 3;
}
if (nor->addr_width == 3 && nor->params->size > 0x1000000) {
if (nor->addr_nbytes == 3 && nor->params->size > 0x1000000) {
/* enable 4-byte addressing if the device exceeds 16MiB */
nor->addr_width = 4;
nor->addr_nbytes = 4;
}
if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) {
dev_dbg(nor->dev, "address width is too large: %u\n",
nor->addr_width);
if (nor->addr_nbytes > SPI_NOR_MAX_ADDR_NBYTES) {
dev_dbg(nor->dev, "The number of address bytes is too large: %u\n",
nor->addr_nbytes);
return -EINVAL;
}
/* Set 4byte opcodes when possible. */
if (nor->addr_width == 4 && nor->flags & SNOR_F_4B_OPCODES &&
if (nor->addr_nbytes == 4 && nor->flags & SNOR_F_4B_OPCODES &&
!(nor->flags & SNOR_F_HAS_4BAIT))
spi_nor_set_4byte_opcodes(nor);
......@@ -2304,7 +2304,7 @@ static int spi_nor_setup(struct spi_nor *nor,
if (ret)
return ret;
return spi_nor_set_addr_width(nor);
return spi_nor_set_addr_nbytes(nor);
}
/**
......@@ -2382,12 +2382,7 @@ static void spi_nor_no_sfdp_init_params(struct spi_nor *nor)
*/
erase_mask = 0;
i = 0;
if (no_sfdp_flags & SECT_4K_PMC) {
erase_mask |= BIT(i);
spi_nor_set_erase_type(&map->erase_type[i], 4096u,
SPINOR_OP_BE_4K_PMC);
i++;
} else if (no_sfdp_flags & SECT_4K) {
if (no_sfdp_flags & SECT_4K) {
erase_mask |= BIT(i);
spi_nor_set_erase_type(&map->erase_type[i], 4096u,
SPINOR_OP_BE_4K);
......@@ -2497,7 +2492,6 @@ static void spi_nor_sfdp_init_params_deprecated(struct spi_nor *nor)
if (spi_nor_parse_sfdp(nor)) {
memcpy(nor->params, &sfdp_params, sizeof(*nor->params));
nor->addr_width = 0;
nor->flags &= ~SNOR_F_4B_OPCODES;
}
}
......@@ -2718,7 +2712,7 @@ static int spi_nor_init(struct spi_nor *nor)
nor->flags & SNOR_F_SWP_IS_VOLATILE))
spi_nor_try_unlock_all(nor);
if (nor->addr_width == 4 &&
if (nor->addr_nbytes == 4 &&
nor->read_proto != SNOR_PROTO_8_8_8_DTR &&
!(nor->flags & SNOR_F_4B_OPCODES)) {
/*
......@@ -2730,7 +2724,7 @@ static int spi_nor_init(struct spi_nor *nor)
*/
WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET,
"enabling reset hack; may not recover from unexpected reboots\n");
nor->params->set_4byte_addr_mode(nor, true);
return nor->params->set_4byte_addr_mode(nor, true);
}
return 0;
......@@ -2845,7 +2839,7 @@ static void spi_nor_put_device(struct mtd_info *mtd)
void spi_nor_restore(struct spi_nor *nor)
{
/* restore the addressing mode */
if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES) &&
if (nor->addr_nbytes == 4 && !(nor->flags & SNOR_F_4B_OPCODES) &&
nor->flags & SNOR_F_BROKEN_RESET)
nor->params->set_4byte_addr_mode(nor, false);
......@@ -2989,7 +2983,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
* - select op codes for (Fast) Read, Page Program and Sector Erase.
* - set the number of dummy cycles (mode cycles + wait states).
* - set the SPI protocols for register and memory accesses.
* - set the address width.
* - set the number of address bytes.
*/
ret = spi_nor_setup(nor, hwcaps);
if (ret)
......@@ -3030,7 +3024,7 @@ static int spi_nor_create_read_dirmap(struct spi_nor *nor)
{
struct spi_mem_dirmap_info info = {
.op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
SPI_MEM_OP_ADDR(nor->addr_width, 0, 0),
SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
SPI_MEM_OP_DATA_IN(0, NULL, 0)),
.offset = 0,
......@@ -3061,7 +3055,7 @@ static int spi_nor_create_write_dirmap(struct spi_nor *nor)
{
struct spi_mem_dirmap_info info = {
.op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0),
SPI_MEM_OP_ADDR(nor->addr_width, 0, 0),
SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(0, NULL, 0)),
.offset = 0,
......
......@@ -84,9 +84,9 @@
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_NO_DATA)
#define SPI_NOR_SECTOR_ERASE_OP(opcode, addr_width, addr) \
#define SPI_NOR_SECTOR_ERASE_OP(opcode, addr_nbytes, addr) \
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
SPI_MEM_OP_ADDR(addr_width, addr, 0), \
SPI_MEM_OP_ADDR(addr_nbytes, addr, 0), \
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_NO_DATA)
......@@ -340,6 +340,11 @@ struct spi_nor_otp {
* @writesize Minimal writable flash unit size. Defaults to 1. Set to
* ECC unit size for ECC-ed flashes.
* @page_size: the page size of the SPI NOR flash memory.
* @addr_nbytes: number of address bytes to send.
* @addr_mode_nbytes: number of address bytes of current address mode. Useful
* when the flash operates with 4B opcodes but needs the
* internal address mode for opcodes that don't have a 4B
* opcode correspondent.
* @rdsr_dummy: dummy cycles needed for Read Status Register command
* in octal DTR mode.
* @rdsr_addr_nbytes: dummy address bytes needed for Read Status Register
......@@ -372,6 +377,8 @@ struct spi_nor_flash_parameter {
u64 size;
u32 writesize;
u32 page_size;
u8 addr_nbytes;
u8 addr_mode_nbytes;
u8 rdsr_dummy;
u8 rdsr_addr_nbytes;
......@@ -429,7 +436,7 @@ struct spi_nor_fixups {
* isn't necessarily called a "sector" by the vendor.
* @n_sectors: the number of sectors.
* @page_size: the flash's page size.
* @addr_width: the flash's address width.
* @addr_nbytes: number of address bytes to send.
*
* @parse_sfdp: true when flash supports SFDP tables. The false value has no
* meaning. If one wants to skip the SFDP tables, one should
......@@ -457,7 +464,6 @@ struct spi_nor_fixups {
* flags are used together with the SPI_NOR_SKIP_SFDP flag.
* SPI_NOR_SKIP_SFDP: skip parsing of SFDP tables.
* SECT_4K: SPINOR_OP_BE_4K works uniformly.
* SECT_4K_PMC: SPINOR_OP_BE_4K_PMC works uniformly.
* SPI_NOR_DUAL_READ: flash supports Dual Read.
* SPI_NOR_QUAD_READ: flash supports Quad Read.
* SPI_NOR_OCTAL_READ: flash supports Octal Read.
......@@ -488,7 +494,7 @@ struct flash_info {
unsigned sector_size;
u16 n_sectors;
u16 page_size;
u16 addr_width;
u8 addr_nbytes;
bool parse_sfdp;
u16 flags;
......@@ -505,7 +511,6 @@ struct flash_info {
u8 no_sfdp_flags;
#define SPI_NOR_SKIP_SFDP BIT(0)
#define SECT_4K BIT(1)
#define SECT_4K_PMC BIT(2)
#define SPI_NOR_DUAL_READ BIT(3)
#define SPI_NOR_QUAD_READ BIT(4)
#define SPI_NOR_OCTAL_READ BIT(5)
......@@ -550,11 +555,11 @@ struct flash_info {
.n_sectors = (_n_sectors), \
.page_size = 256, \
#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width) \
#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_nbytes) \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = (_page_size), \
.addr_width = (_addr_width), \
.addr_nbytes = (_addr_nbytes), \
.flags = SPI_NOR_NO_ERASE | SPI_NOR_NO_FR, \
#define OTP_INFO(_len, _n_regions, _base, _offset) \
......
......@@ -86,7 +86,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
seq_printf(s, "size\t\t%s\n", buf);
seq_printf(s, "write size\t%u\n", params->writesize);
seq_printf(s, "page size\t%u\n", params->page_size);
seq_printf(s, "address width\t%u\n", nor->addr_width);
seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes);
seq_puts(s, "flags\t\t");
spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names));
......
......@@ -13,7 +13,7 @@ static const struct flash_info esmt_nor_parts[] = {
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64)
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
NO_SFDP_FLAGS(SECT_4K) },
{ "f25l32qa", INFO(0x8c4116, 0, 64 * 1024, 64)
{ "f25l32qa-2s", INFO(0x8c4116, 0, 64 * 1024, 64)
FLAGS(SPI_NOR_HAS_LOCK)
NO_SFDP_FLAGS(SECT_4K) },
{ "f25l64qa", INFO(0x8c4117, 0, 64 * 1024, 128)
......
......@@ -14,13 +14,13 @@ is25lp256_post_bfpt_fixups(struct spi_nor *nor,
const struct sfdp_bfpt *bfpt)
{
/*
* IS25LP256 supports 4B opcodes, but the BFPT advertises a
* BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width.
* Overwrite the address width advertised by the BFPT.
* IS25LP256 supports 4B opcodes, but the BFPT advertises
* BFPT_DWORD1_ADDRESS_BYTES_3_ONLY.
* Overwrite the number of address bytes advertised by the BFPT.
*/
if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) ==
BFPT_DWORD1_ADDRESS_BYTES_3_ONLY)
nor->addr_width = 4;
nor->params->addr_nbytes = 4;
return 0;
}
......@@ -29,6 +29,21 @@ static const struct spi_nor_fixups is25lp256_fixups = {
.post_bfpt = is25lp256_post_bfpt_fixups,
};
static void pm25lv_nor_late_init(struct spi_nor *nor)
{
struct spi_nor_erase_map *map = &nor->params->erase_map;
int i;
/* The PM25LV series has a different 4k sector erase opcode */
for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++)
if (map->erase_type[i].size == 4096)
map->erase_type[i].opcode = SPINOR_OP_BE_4K_PMC;
}
static const struct spi_nor_fixups pm25lv_nor_fixups = {
.late_init = pm25lv_nor_late_init,
};
static const struct flash_info issi_nor_parts[] = {
/* ISSI */
{ "is25cd512", INFO(0x7f9d20, 0, 32 * 1024, 2)
......@@ -62,9 +77,13 @@ static const struct flash_info issi_nor_parts[] = {
/* PMC */
{ "pm25lv512", INFO(0, 0, 32 * 1024, 2)
NO_SFDP_FLAGS(SECT_4K_PMC) },
NO_SFDP_FLAGS(SECT_4K)
.fixups = &pm25lv_nor_fixups
},
{ "pm25lv010", INFO(0, 0, 32 * 1024, 4)
NO_SFDP_FLAGS(SECT_4K_PMC) },
NO_SFDP_FLAGS(SECT_4K)
.fixups = &pm25lv_nor_fixups
},
{ "pm25lq032", INFO(0x7f9d46, 0, 64 * 1024, 64)
NO_SFDP_FLAGS(SECT_4K) },
};
......
......@@ -399,8 +399,16 @@ static int micron_st_nor_ready(struct spi_nor *nor)
return sr_ready;
ret = micron_st_nor_read_fsr(nor, nor->bouncebuf);
if (ret)
return ret;
if (ret) {
/*
* Some controllers, such as Intel SPI, do not support low
* level operations such as reading the flag status
* register. They only expose small amount of high level
* operations to the software. If this is the case we use
* only the status register value.
*/
return ret == -EOPNOTSUPP ? sr_ready : ret;
}
if (nor->bouncebuf[0] & (FSR_E_ERR | FSR_P_ERR)) {
if (nor->bouncebuf[0] & FSR_E_ERR)
......
......@@ -35,13 +35,13 @@
*/
int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf)
{
u8 addr_width, read_opcode, read_dummy;
u8 addr_nbytes, read_opcode, read_dummy;
struct spi_mem_dirmap_desc *rdesc;
enum spi_nor_protocol read_proto;
int ret;
read_opcode = nor->read_opcode;
addr_width = nor->addr_width;
addr_nbytes = nor->addr_nbytes;
read_dummy = nor->read_dummy;
read_proto = nor->read_proto;
rdesc = nor->dirmap.rdesc;
......@@ -54,7 +54,7 @@ int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf)
ret = spi_nor_read_data(nor, addr, len, buf);
nor->read_opcode = read_opcode;
nor->addr_width = addr_width;
nor->addr_nbytes = addr_nbytes;
nor->read_dummy = read_dummy;
nor->read_proto = read_proto;
nor->dirmap.rdesc = rdesc;
......@@ -85,11 +85,11 @@ int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len,
{
enum spi_nor_protocol write_proto;
struct spi_mem_dirmap_desc *wdesc;
u8 addr_width, program_opcode;
u8 addr_nbytes, program_opcode;
int ret, written;
program_opcode = nor->program_opcode;
addr_width = nor->addr_width;
addr_nbytes = nor->addr_nbytes;
write_proto = nor->write_proto;
wdesc = nor->dirmap.wdesc;
......@@ -113,7 +113,7 @@ int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len,
out:
nor->program_opcode = program_opcode;
nor->addr_width = addr_width;
nor->addr_nbytes = addr_nbytes;
nor->write_proto = write_proto;
nor->dirmap.wdesc = wdesc;
......
......@@ -134,7 +134,7 @@ struct sfdp_4bait {
/**
* spi_nor_read_raw() - raw read of serial flash memory. read_opcode,
* addr_width and read_dummy members of the struct spi_nor
* addr_nbytes and read_dummy members of the struct spi_nor
* should be previously
* set.
* @nor: pointer to a 'struct spi_nor'
......@@ -178,21 +178,21 @@ static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf)
static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
size_t len, void *buf)
{
u8 addr_width, read_opcode, read_dummy;
u8 addr_nbytes, read_opcode, read_dummy;
int ret;
read_opcode = nor->read_opcode;
addr_width = nor->addr_width;
addr_nbytes = nor->addr_nbytes;
read_dummy = nor->read_dummy;
nor->read_opcode = SPINOR_OP_RDSFDP;
nor->addr_width = 3;
nor->addr_nbytes = 3;
nor->read_dummy = 8;
ret = spi_nor_read_raw(nor, addr, len, buf);
nor->read_opcode = read_opcode;
nor->addr_width = addr_width;
nor->addr_nbytes = addr_nbytes;
nor->read_dummy = read_dummy;
return ret;
......@@ -462,11 +462,13 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
switch (bfpt.dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) {
case BFPT_DWORD1_ADDRESS_BYTES_3_ONLY:
case BFPT_DWORD1_ADDRESS_BYTES_3_OR_4:
nor->addr_width = 3;
params->addr_nbytes = 3;
params->addr_mode_nbytes = 3;
break;
case BFPT_DWORD1_ADDRESS_BYTES_4_ONLY:
nor->addr_width = 4;
params->addr_nbytes = 4;
params->addr_mode_nbytes = 4;
break;
default:
......@@ -637,12 +639,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
}
/**
* spi_nor_smpt_addr_width() - return the address width used in the
* spi_nor_smpt_addr_nbytes() - return the number of address bytes used in the
* configuration detection command.
* @nor: pointer to a 'struct spi_nor'
* @settings: configuration detection command descriptor, dword1
*/
static u8 spi_nor_smpt_addr_width(const struct spi_nor *nor, const u32 settings)
static u8 spi_nor_smpt_addr_nbytes(const struct spi_nor *nor, const u32 settings)
{
switch (settings & SMPT_CMD_ADDRESS_LEN_MASK) {
case SMPT_CMD_ADDRESS_LEN_0:
......@@ -653,7 +655,7 @@ static u8 spi_nor_smpt_addr_width(const struct spi_nor *nor, const u32 settings)
return 4;
case SMPT_CMD_ADDRESS_LEN_USE_CURRENT:
default:
return nor->addr_width;
return nor->params->addr_mode_nbytes;
}
}
......@@ -690,7 +692,7 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
u32 addr;
int err;
u8 i;
u8 addr_width, read_opcode, read_dummy;
u8 addr_nbytes, read_opcode, read_dummy;
u8 read_data_mask, map_id;
/* Use a kmalloc'ed bounce buffer to guarantee it is DMA-able. */
......@@ -698,7 +700,7 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
if (!buf)
return ERR_PTR(-ENOMEM);
addr_width = nor->addr_width;
addr_nbytes = nor->addr_nbytes;
read_dummy = nor->read_dummy;
read_opcode = nor->read_opcode;
......@@ -709,7 +711,7 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
break;
read_data_mask = SMPT_CMD_READ_DATA(smpt[i]);
nor->addr_width = spi_nor_smpt_addr_width(nor, smpt[i]);
nor->addr_nbytes = spi_nor_smpt_addr_nbytes(nor, smpt[i]);
nor->read_dummy = spi_nor_smpt_read_dummy(nor, smpt[i]);
nor->read_opcode = SMPT_CMD_OPCODE(smpt[i]);
addr = smpt[i + 1];
......@@ -756,7 +758,7 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
/* fall through */
out:
kfree(buf);
nor->addr_width = addr_width;
nor->addr_nbytes = addr_nbytes;
nor->read_dummy = read_dummy;
nor->read_opcode = read_opcode;
return ret;
......@@ -1044,7 +1046,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
/*
* We need at least one 4-byte op code per read, program and erase
* operation; the .read(), .write() and .erase() hooks share the
* nor->addr_width value.
* nor->addr_nbytes value.
*/
if (!read_hwcaps || !pp_hwcaps || !erase_mask)
goto out;
......@@ -1098,7 +1100,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
* Spansion memory. However this quirk is no longer needed with new
* SFDP compliant memories.
*/
nor->addr_width = 4;
params->addr_nbytes = 4;
nor->flags |= SNOR_F_4B_OPCODES | SNOR_F_HAS_4BAIT;
/* fall through */
......
......@@ -14,6 +14,8 @@
#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */
#define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */
#define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */
#define SPINOR_REG_CYPRESS_CFR1V 0x00800002
#define SPINOR_REG_CYPRESS_CFR1V_QUAD_EN BIT(1) /* Quad Enable */
#define SPINOR_REG_CYPRESS_CFR2V 0x00800003
#define SPINOR_REG_CYPRESS_CFR2V_MEMLAT_11_24 0xb
#define SPINOR_REG_CYPRESS_CFR3V 0x00800004
......@@ -113,6 +115,150 @@ static int cypress_nor_octal_dtr_dis(struct spi_nor *nor)
return 0;
}
/**
* cypress_nor_quad_enable_volatile() - enable Quad I/O mode in volatile
* register.
* @nor: pointer to a 'struct spi_nor'
*
* It is recommended to update volatile registers in the field application due
* to a risk of the non-volatile registers corruption by power interrupt. This
* function sets Quad Enable bit in CFR1 volatile. If users set the Quad Enable
* bit in the CFR1 non-volatile in advance (typically by a Flash programmer
* before mounting Flash on PCB), the Quad Enable bit in the CFR1 volatile is
* also set during Flash power-up.
*
* Return: 0 on success, -errno otherwise.
*/
static int cypress_nor_quad_enable_volatile(struct spi_nor *nor)
{
struct spi_mem_op op;
u8 addr_mode_nbytes = nor->params->addr_mode_nbytes;
u8 cfr1v_written;
int ret;
op = (struct spi_mem_op)
CYPRESS_NOR_RD_ANY_REG_OP(addr_mode_nbytes,
SPINOR_REG_CYPRESS_CFR1V,
nor->bouncebuf);
ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
if (nor->bouncebuf[0] & SPINOR_REG_CYPRESS_CFR1V_QUAD_EN)
return 0;
/* Update the Quad Enable bit. */
nor->bouncebuf[0] |= SPINOR_REG_CYPRESS_CFR1V_QUAD_EN;
op = (struct spi_mem_op)
CYPRESS_NOR_WR_ANY_REG_OP(addr_mode_nbytes,
SPINOR_REG_CYPRESS_CFR1V, 1,
nor->bouncebuf);
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
cfr1v_written = nor->bouncebuf[0];
/* Read back and check it. */
op = (struct spi_mem_op)
CYPRESS_NOR_RD_ANY_REG_OP(addr_mode_nbytes,
SPINOR_REG_CYPRESS_CFR1V,
nor->bouncebuf);
ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
if (nor->bouncebuf[0] != cfr1v_written) {
dev_err(nor->dev, "CFR1: Read back test failed\n");
return -EIO;
}
return 0;
}
/**
* cypress_nor_set_page_size() - Set page size which corresponds to the flash
* configuration.
* @nor: pointer to a 'struct spi_nor'
*
* The BFPT table advertises a 512B or 256B page size depending on part but the
* page size is actually configurable (with the default being 256B). Read from
* CFR3V[4] and set the correct size.
*
* Return: 0 on success, -errno otherwise.
*/
static int cypress_nor_set_page_size(struct spi_nor *nor)
{
struct spi_mem_op op =
CYPRESS_NOR_RD_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR3V,
nor->bouncebuf);
int ret;
ret = spi_nor_read_any_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
if (nor->bouncebuf[0] & SPINOR_REG_CYPRESS_CFR3V_PGSZ)
nor->params->page_size = 512;
else
nor->params->page_size = 256;
return 0;
}
static int
s25hx_t_post_bfpt_fixup(struct spi_nor *nor,
const struct sfdp_parameter_header *bfpt_header,
const struct sfdp_bfpt *bfpt)
{
/* Replace Quad Enable with volatile version */
nor->params->quad_enable = cypress_nor_quad_enable_volatile;
return cypress_nor_set_page_size(nor);
}
static void s25hx_t_post_sfdp_fixup(struct spi_nor *nor)
{
struct spi_nor_erase_type *erase_type =
nor->params->erase_map.erase_type;
unsigned int i;
/*
* In some parts, 3byte erase opcodes are advertised by 4BAIT.
* Convert them to 4byte erase opcodes.
*/
for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
switch (erase_type[i].opcode) {
case SPINOR_OP_SE:
erase_type[i].opcode = SPINOR_OP_SE_4B;
break;
case SPINOR_OP_BE_4K:
erase_type[i].opcode = SPINOR_OP_BE_4K_4B;
break;
default:
break;
}
}
}
static void s25hx_t_late_init(struct spi_nor *nor)
{
struct spi_nor_flash_parameter *params = nor->params;
/* Fast Read 4B requires mode cycles */
params->reads[SNOR_CMD_READ_FAST].num_mode_clocks = 8;
/* The writesize should be ECC data unit size */
params->writesize = 16;
}
static struct spi_nor_fixups s25hx_t_fixups = {
.post_bfpt = s25hx_t_post_bfpt_fixup,
.post_sfdp = s25hx_t_post_sfdp_fixup,
.late_init = s25hx_t_late_init,
};
/**
* cypress_nor_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
* @nor: pointer to a 'struct spi_nor'
......@@ -167,28 +313,7 @@ static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
const struct sfdp_parameter_header *bfpt_header,
const struct sfdp_bfpt *bfpt)
{
/*
* The BFPT table advertises a 512B page size but the page size is
* actually configurable (with the default being 256B). Read from
* CFR3V[4] and set the correct size.
*/
struct spi_mem_op op =
CYPRESS_NOR_RD_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR3V,
nor->bouncebuf);
int ret;
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
ret = spi_mem_exec_op(nor->spimem, &op);
if (ret)
return ret;
if (nor->bouncebuf[0] & SPINOR_REG_CYPRESS_CFR3V_PGSZ)
nor->params->page_size = 512;
else
nor->params->page_size = 256;
return 0;
return cypress_nor_set_page_size(nor);
}
static const struct spi_nor_fixups s28hs512t_fixups = {
......@@ -310,6 +435,22 @@ static const struct flash_info spansion_nor_parts[] = {
{ "s25fl256l", INFO(0x016019, 0, 64 * 1024, 512)
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
{ "s25hl512t", INFO6(0x342a1a, 0x0f0390, 256 * 1024, 256)
PARSE_SFDP
MFR_FLAGS(USE_CLSR)
.fixups = &s25hx_t_fixups },
{ "s25hl01gt", INFO6(0x342a1b, 0x0f0390, 256 * 1024, 512)
PARSE_SFDP
MFR_FLAGS(USE_CLSR)
.fixups = &s25hx_t_fixups },
{ "s25hs512t", INFO6(0x342b1a, 0x0f0390, 256 * 1024, 256)
PARSE_SFDP
MFR_FLAGS(USE_CLSR)
.fixups = &s25hx_t_fixups },
{ "s25hs01gt", INFO6(0x342b1b, 0x0f0390, 256 * 1024, 512)
PARSE_SFDP
MFR_FLAGS(USE_CLSR)
.fixups = &s25hx_t_fixups },
{ "cy15x104q", INFO6(0x042cc2, 0x7f7f7f, 512 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE) },
{ "s28hs512t", INFO(0x345b1a, 0, 256 * 1024, 256)
......
......@@ -31,7 +31,7 @@
.sector_size = (8 * (_page_size)), \
.n_sectors = (_n_sectors), \
.page_size = (_page_size), \
.addr_width = 3, \
.addr_nbytes = 3, \
.flags = SPI_NOR_NO_FR
/* Xilinx S3AN share MFR with Atmel SPI NOR */
......
......@@ -89,9 +89,7 @@ int hyperbus_register_device(struct hyperbus_device *hbdev);
/**
* hyperbus_unregister_device - deregister HyperBus slave memory device
* @hbdev: hyperbus_device to be unregistered
*
* Return: 0 for success, others for failure.
*/
int hyperbus_unregister_device(struct hyperbus_device *hbdev);
void hyperbus_unregister_device(struct hyperbus_device *hbdev);
#endif /* __LINUX_MTD_HYPERBUS_H__ */
......@@ -351,7 +351,7 @@ struct spi_nor_flash_parameter;
* @bouncebuf_size: size of the bounce buffer
* @info: SPI NOR part JEDEC MFR ID and other info
* @manufacturer: SPI NOR manufacturer
* @addr_width: number of address bytes
* @addr_nbytes: number of address bytes
* @erase_opcode: the opcode for erasing a sector
* @read_opcode: the read opcode
* @read_dummy: the dummy needed by the read operation
......@@ -381,7 +381,7 @@ struct spi_nor {
size_t bouncebuf_size;
const struct flash_info *info;
const struct spi_nor_manufacturer *manufacturer;
u8 addr_width;
u8 addr_nbytes;
u8 erase_opcode;
u8 read_opcode;
u8 read_dummy;
......
......@@ -260,6 +260,7 @@ struct spinand_manufacturer {
};
/* SPI NAND manufacturers */
extern const struct spinand_manufacturer ato_spinand_manufacturer;
extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
extern const struct spinand_manufacturer micron_spinand_manufacturer;
......
......@@ -69,8 +69,8 @@ enum {
* struct mtd_write_req - data structure for requesting a write operation
*
* @start: start address
* @len: length of data buffer
* @ooblen: length of OOB buffer
* @len: length of data buffer (only lower 32 bits are used)
* @ooblen: length of OOB buffer (only lower 32 bits are used)
* @usr_data: user-provided data buffer
* @usr_oob: user-provided OOB buffer
* @mode: MTD mode (see "MTD operation modes")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment