MTD updates for 4.11:
General: - new kernel API for retrieving max bad blocks per die (not used yet) - track (and expose via sysfs) a partition's device tree node - support a "label" device tree property for naming an MTD NAND: - sunxi: avoid busy-waiting for NAND events - ifc: fix ECC handling on IFC v1.0 - OX820: add explicit dependency on ARCH_OXNAS in Kconfig - core: add a new manufacture ID and fix a kernel-doc warning - fsmc: kill pdata support - lpc32xx_slc: remove unneeded NULL check - support dynamic "max bad blocks" detection via ONFI SPI NOR: - add support for the 4-byte address instruction set - add support for new memory parts - add support to S3AN memories - add support to the Intel SPI controller - add support to the Aspeed AST2400 and AST2550 controllers - support max SPI message sizes in m25p80_read() - fixes for the Candence and Freescale QSPI drivers Other: - add support for Gemini flash probing - bcm47xxsflash: add support for reading outside memory-mapped window - bcm47xxparts: extend to support multiple TRX partitions - misc fixes and typos -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIbBAABAgAGBQJYoRq6AAoJEFySrpd9RFgtZFYP9Rs26KsokTVyjFQ8OZuDZpzp 0b6SNYKhxzW6FpyMNuG8lK9+TIbmpuQJv8fJY8ns4ET9x5qEYZjlBwr0Oa/g+z+3 2o4YHc6wx+j2ErTNfas74vzVmCafBycf7tpXizlEpDJekoqysvWb0k5zTqXFsURk 5l93xZ0t5Xwe7OsCIcoEclTfvWxnge0V6a34jhHo1g2lc0HDi5HlChv+9wyvS8vR aCjVoYULErR9/2i65MUzAgN9UtPuEEwRpZz2KYYuBF0xd1EI8ZhDt43jhRnL9ZpK 7kKwS871WqBGhvnQPRDhhx70cvwsesrd+W601d+O+li2odd6eeWm03XXHbatzYtK +MDDzFKp0b/43wVxHwmNj/N7/VXwtzr5mO4pTjqIB3lIeJRhvUKkqIVeo/YRFYo+ 8YizldzA/HbyqzcTazw2i93XbqL2TcbOAkZKTBKE9/tG+omJuLnRRt2tYelS58wv i4WFRDA4vADNKGU0XmikmYkO3RDDtGtisum85KXHW6/YtkR5+aRveqW08KuZjr7T i3ivjS4wXw6jU3DERTcLX65dOTSrFYXTMtsq2o1JUEOKlT+dNwIk6FNVPDEhMPQi ZZVSduUMOxjOSSGltal6FPXsSHSy+dnnfxfL2XCWyYxJiaNzeqFhkP6NXXVQso6I aH8XIWqY/Q2deF+oIjc= =6y0d -----END PGP SIGNATURE----- Merge tag 'for-linus-20170212' of git://git.infradead.org/linux-mtd Pull MTD updates from Brian Norris: "MTD updates for 4.11: General: - new kernel API for retrieving max bad blocks per die (not used yet) - track (and expose via sysfs) a partition's device tree node - support a "label" device tree property for naming an MTD NAND: - sunxi: avoid busy-waiting for NAND events - ifc: fix ECC handling on IFC v1.0 - OX820: add explicit dependency on ARCH_OXNAS in Kconfig - core: add a new manufacture ID and fix a kernel-doc warning - fsmc: kill pdata support - lpc32xx_slc: remove unneeded NULL check - support dynamic "max bad blocks" detection via ONFI SPI NOR: - add support for the 4-byte address instruction set - add support for new memory parts - add support to S3AN memories - add support to the Intel SPI controller - add support to the Aspeed AST2400 and AST2550 controllers - support max SPI message sizes in m25p80_read() - fixes for the Candence and Freescale QSPI drivers Other: - add support for Gemini flash probing - bcm47xxsflash: add support for reading outside memory-mapped window - bcm47xxparts: extend to support multiple TRX partitions - misc fixes and typos Extra note: we've pulled in an MFD subtree from Lee Jones as a dependency for a new Intel SPI NOR driver" [ Kudos to Brian for sending pull request a week early: "I refuse to acknowledge the existence of 4.10-rc8 and am therefore sending our MTD changes for 4.11 now" which is all good ] * tag 'for-linus-20170212' of git://git.infradead.org/linux-mtd: (52 commits) mtd: aspeed: remove redundant dev_err call in aspeed_smc_probe() dt-bindings: mtd: add a common label property to all mtd devices mtd: name the mtd device with an optional label property mtd: physmap_of: fixup gemini/versatile dependencies mtd: spi-nor: cqspi: remove redundant dead code on error return check Documentation: mtk-quadspi: update DT bindings mtd: fsl-quadspi: Rename SEQID_QUAD_READ to SEQID_READ mtd:fsl-quadspi:use the property fields of SPI-NOR mtd: spi-nor: Add support for gd25q16 mtd: spi-nor: Fix S3AN addressing calculation mtd: aspeed: fix compile warning in aspeed_smc_read_from_ahb() mtd: spi-nor: add dt support for Everspin MRAMs mtd: spi-nor: Add lock/unlock support for f25l32pa mtd: spi-nor: add a stateless method to support memory size above 128Mib mtd: spi-nor: rename SPINOR_OP_* macros of the 4-byte address op codes mtd: m25p80: consider max message size in m25p80_read mtd: spi-nor: bindings for the Aspeed memory controllers mtd: aspeed: add memory controllers for the Aspeed AST2400 SoC mtd: spi-nor: add memory controllers for the Aspeed AST2500 SoC mtd: spi-nor: remove WARN_ONCE() message in spi_nor_write() ...
This commit is contained in:
commit
ce1bf97965
|
@ -0,0 +1,51 @@
|
|||
* Aspeed Firmware Memory controller
|
||||
* Aspeed SPI Flash Memory Controller
|
||||
|
||||
The Firmware Memory Controller in the Aspeed AST2500 SoC supports
|
||||
three chip selects, two of which are always of SPI type and the third
|
||||
can be SPI or NOR type flash. These bindings only describe SPI.
|
||||
|
||||
The two SPI flash memory controllers in the AST2500 each support two
|
||||
chip selects.
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be one of
|
||||
"aspeed,ast2400-fmc" for the AST2400 Firmware Memory Controller
|
||||
"aspeed,ast2400-spi" for the AST2400 SPI Flash memory Controller
|
||||
"aspeed,ast2500-fmc" for the AST2500 Firmware Memory Controller
|
||||
"aspeed,ast2500-spi" for the AST2500 SPI flash memory controllers
|
||||
|
||||
- reg : the first contains the control register location and length,
|
||||
the second contains the memory window mapping address and length
|
||||
- #address-cells : must be 1 corresponding to chip select child binding
|
||||
- #size-cells : must be 0 corresponding to chip select child binding
|
||||
|
||||
Optional properties:
|
||||
- interrupts : Should contain the interrupt for the dma device if an
|
||||
FMC
|
||||
|
||||
The child nodes are the SPI flash modules which must have a compatible
|
||||
property as specified in bindings/mtd/jedec,spi-nor.txt
|
||||
|
||||
Optionally, the child node can contain properties for SPI mode (may be
|
||||
ignored):
|
||||
- spi-max-frequency - max frequency of spi bus
|
||||
|
||||
|
||||
Example:
|
||||
fmc: fmc@1e620000 {
|
||||
compatible = "aspeed,ast2500-fmc";
|
||||
reg = < 0x1e620000 0x94
|
||||
0x20000000 0x02000000 >;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
interrupts = <19>;
|
||||
flash@0 {
|
||||
reg = < 0 >;
|
||||
compatible = "jedec,spi-nor";
|
||||
/* spi-max-frequency = <>; */
|
||||
/* m25p,fast-read; */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
* Common properties of all MTD devices
|
||||
|
||||
Optional properties:
|
||||
- label: user-defined MTD device name. Can be used to assign user
|
||||
friendly names to MTD devices (instead of the flash model or flash
|
||||
controller based name) in order to ease flash device identification
|
||||
and/or describe what they are used for.
|
||||
|
||||
Example:
|
||||
|
||||
flash@0 {
|
||||
label = "System-firmware";
|
||||
|
||||
/* flash type specific properties */
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
Flash device on Cortina Systems Gemini SoC
|
||||
|
||||
This flash is regular CFI compatible (Intel or AMD extended) flash chips with
|
||||
some special bits that can be controlled by the machine's system controller.
|
||||
|
||||
Required properties:
|
||||
- compatible : must be "cortina,gemini-flash", "cfi-flash";
|
||||
- reg : memory address for the flash chip
|
||||
- syscon : must be a phandle to the system controller
|
||||
- bank-width : width in bytes of flash interface, should be <2>
|
||||
|
||||
For the rest of the properties, see mtd-physmap.txt.
|
||||
|
||||
The device tree may optionally contain sub-nodes describing partitions of the
|
||||
address space. See partition.txt for more detail.
|
||||
|
||||
Example:
|
||||
|
||||
flash@30000000 {
|
||||
compatible = "cortina,gemini-flash", "cfi-flash";
|
||||
reg = <0x30000000 0x01000000>;
|
||||
syscon = <&syscon>;
|
||||
bank-width = <2>;
|
||||
};
|
|
@ -14,6 +14,8 @@ Required properties:
|
|||
at25df641
|
||||
at26df081a
|
||||
mr25h256
|
||||
mr25h10
|
||||
mr25h40
|
||||
mx25l4005a
|
||||
mx25l1606e
|
||||
mx25l6405d
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
* Serial NOR flash controller for MTK MT81xx (and similar)
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "mediatek,mt8173-nor";
|
||||
- compatible: The possible values are:
|
||||
"mediatek,mt2701-nor"
|
||||
"mediatek,mt7623-nor"
|
||||
"mediatek,mt8173-nor"
|
||||
For mt8173, compatible should be "mediatek,mt8173-nor".
|
||||
For every other SoC, should contain both the SoC-specific compatible string
|
||||
and "mediatek,mt8173-nor".
|
||||
- reg: physical base address and length of the controller's register
|
||||
- clocks: the phandle of the clocks needed by the nor controller
|
||||
- clock-names: the names of the clocks
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
Upgrading BIOS using intel-spi
|
||||
------------------------------
|
||||
|
||||
Many Intel CPUs like Baytrail and Braswell include SPI serial flash host
|
||||
controller which is used to hold BIOS and other platform specific data.
|
||||
Since contents of the SPI serial flash is crucial for machine to function,
|
||||
it is typically protected by different hardware protection mechanisms to
|
||||
avoid accidental (or on purpose) overwrite of the content.
|
||||
|
||||
Not all manufacturers protect the SPI serial flash, mainly because it
|
||||
allows upgrading the BIOS image directly from an OS.
|
||||
|
||||
The intel-spi driver makes it possible to read and write the SPI serial
|
||||
flash, if certain protection bits are not set and locked. If it finds
|
||||
any of them set, the whole MTD device is made read-only to prevent
|
||||
partial overwrites. By default the driver exposes SPI serial flash
|
||||
contents as read-only but it can be changed from kernel command line,
|
||||
passing "intel-spi.writeable=1".
|
||||
|
||||
Please keep in mind that overwriting the BIOS image on SPI serial flash
|
||||
might render the machine unbootable and requires special equipment like
|
||||
Dediprog to revive. You have been warned!
|
||||
|
||||
Below are the steps how to upgrade MinnowBoard MAX BIOS directly from
|
||||
Linux.
|
||||
|
||||
1) Download and extract the latest Minnowboard MAX BIOS SPI image
|
||||
[1]. At the time writing this the latest image is v92.
|
||||
|
||||
2) Install mtd-utils package [2]. We need this in order to erase the SPI
|
||||
serial flash. Distros like Debian and Fedora have this prepackaged with
|
||||
name "mtd-utils".
|
||||
|
||||
3) Add "intel-spi.writeable=1" to the kernel command line and reboot
|
||||
the board (you can also reload the driver passing "writeable=1" as
|
||||
module parameter to modprobe).
|
||||
|
||||
4) Once the board is up and running again, find the right MTD partition
|
||||
(it is named as "BIOS"):
|
||||
|
||||
# cat /proc/mtd
|
||||
dev: size erasesize name
|
||||
mtd0: 00800000 00001000 "BIOS"
|
||||
|
||||
So here it will be /dev/mtd0 but it may vary.
|
||||
|
||||
5) Make backup of the existing image first:
|
||||
|
||||
# dd if=/dev/mtd0ro of=bios.bak
|
||||
16384+0 records in
|
||||
16384+0 records out
|
||||
8388608 bytes (8.4 MB) copied, 10.0269 s, 837 kB/s
|
||||
|
||||
6) Verify the backup
|
||||
|
||||
# sha1sum /dev/mtd0ro bios.bak
|
||||
fdbb011920572ca6c991377c4b418a0502668b73 /dev/mtd0ro
|
||||
fdbb011920572ca6c991377c4b418a0502668b73 bios.bak
|
||||
|
||||
The SHA1 sums must match. Otherwise do not continue any further!
|
||||
|
||||
7) Erase the SPI serial flash. After this step, do not reboot the
|
||||
board! Otherwise it will not start anymore.
|
||||
|
||||
# flash_erase /dev/mtd0 0 0
|
||||
Erasing 4 Kibyte @ 7ff000 -- 100 % complete
|
||||
|
||||
8) Once completed without errors you can write the new BIOS image:
|
||||
|
||||
# dd if=MNW2MAX1.X64.0092.R01.1605221712.bin of=/dev/mtd0
|
||||
|
||||
9) Verify that the new content of the SPI serial flash matches the new
|
||||
BIOS image:
|
||||
|
||||
# sha1sum /dev/mtd0ro MNW2MAX1.X64.0092.R01.1605221712.bin
|
||||
9b4df9e4be2057fceec3a5529ec3d950836c87a2 /dev/mtd0ro
|
||||
9b4df9e4be2057fceec3a5529ec3d950836c87a2 MNW2MAX1.X64.0092.R01.1605221712.bin
|
||||
|
||||
The SHA1 sums should match.
|
||||
|
||||
10) Now you can reboot your board and observe the new BIOS starting up
|
||||
properly.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
[1] https://firmware.intel.com/sites/default/files/MinnowBoard.MAX_.X64.92.R01.zip
|
||||
[2] http://www.linux-mtd.infradead.org/
|
|
@ -56,6 +56,7 @@
|
|||
* document number TBD : Wildcat Point-LP
|
||||
* document number TBD : 9 Series
|
||||
* document number TBD : Lewisburg
|
||||
* document number TBD : Apollo Lake SoC
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
@ -83,6 +84,17 @@
|
|||
#define ACPIBASE_GCS_OFF 0x3410
|
||||
#define ACPIBASE_GCS_END 0x3414
|
||||
|
||||
#define SPIBASE_BYT 0x54
|
||||
#define SPIBASE_BYT_SZ 512
|
||||
#define SPIBASE_BYT_EN BIT(1)
|
||||
|
||||
#define SPIBASE_LPT 0x3800
|
||||
#define SPIBASE_LPT_SZ 512
|
||||
#define BCR 0xdc
|
||||
#define BCR_WPD BIT(0)
|
||||
|
||||
#define SPIBASE_APL_SZ 4096
|
||||
|
||||
#define GPIOBASE_ICH0 0x58
|
||||
#define GPIOCTRL_ICH0 0x5C
|
||||
#define GPIOBASE_ICH6 0x48
|
||||
|
@ -133,6 +145,12 @@ static struct resource gpio_ich_res[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct resource intel_spi_res[] = {
|
||||
{
|
||||
.flags = IORESOURCE_MEM,
|
||||
}
|
||||
};
|
||||
|
||||
static struct mfd_cell lpc_ich_wdt_cell = {
|
||||
.name = "iTCO_wdt",
|
||||
.num_resources = ARRAY_SIZE(wdt_ich_res),
|
||||
|
@ -147,6 +165,14 @@ static struct mfd_cell lpc_ich_gpio_cell = {
|
|||
.ignore_resource_conflicts = true,
|
||||
};
|
||||
|
||||
|
||||
static struct mfd_cell lpc_ich_spi_cell = {
|
||||
.name = "intel-spi",
|
||||
.num_resources = ARRAY_SIZE(intel_spi_res),
|
||||
.resources = intel_spi_res,
|
||||
.ignore_resource_conflicts = true,
|
||||
};
|
||||
|
||||
/* chipset related info */
|
||||
enum lpc_chipsets {
|
||||
LPC_ICH = 0, /* ICH */
|
||||
|
@ -216,6 +242,7 @@ enum lpc_chipsets {
|
|||
LPC_BRASWELL, /* Braswell SoC */
|
||||
LPC_LEWISBURG, /* Lewisburg */
|
||||
LPC_9S, /* 9 Series */
|
||||
LPC_APL, /* Apollo Lake SoC */
|
||||
};
|
||||
|
||||
static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
|
@ -494,10 +521,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||
.name = "Lynx Point",
|
||||
.iTCO_version = 2,
|
||||
.gpio_version = ICH_V5_GPIO,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_LPT_LP] = {
|
||||
.name = "Lynx Point_LP",
|
||||
.iTCO_version = 2,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_WBG] = {
|
||||
.name = "Wellsburg",
|
||||
|
@ -511,6 +540,7 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||
[LPC_BAYTRAIL] = {
|
||||
.name = "Bay Trail SoC",
|
||||
.iTCO_version = 3,
|
||||
.spi_type = INTEL_SPI_BYT,
|
||||
},
|
||||
[LPC_COLETO] = {
|
||||
.name = "Coleto Creek",
|
||||
|
@ -519,10 +549,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||
[LPC_WPT_LP] = {
|
||||
.name = "Wildcat Point_LP",
|
||||
.iTCO_version = 2,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_BRASWELL] = {
|
||||
.name = "Braswell SoC",
|
||||
.iTCO_version = 3,
|
||||
.spi_type = INTEL_SPI_BYT,
|
||||
},
|
||||
[LPC_LEWISBURG] = {
|
||||
.name = "Lewisburg",
|
||||
|
@ -533,6 +565,10 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||
.iTCO_version = 2,
|
||||
.gpio_version = ICH_V5_GPIO,
|
||||
},
|
||||
[LPC_APL] = {
|
||||
.name = "Apollo Lake SoC",
|
||||
.spi_type = INTEL_SPI_BXT,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -681,6 +717,7 @@ static const struct pci_device_id lpc_ich_ids[] = {
|
|||
{ PCI_VDEVICE(INTEL, 0x3b14), LPC_3420},
|
||||
{ PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
|
||||
{ PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
|
||||
{ PCI_VDEVICE(INTEL, 0x5ae8), LPC_APL},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
|
||||
|
@ -1056,6 +1093,94 @@ wdt_done:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int lpc_ich_init_spi(struct pci_dev *dev)
|
||||
{
|
||||
struct lpc_ich_priv *priv = pci_get_drvdata(dev);
|
||||
struct resource *res = &intel_spi_res[0];
|
||||
struct intel_spi_boardinfo *info;
|
||||
u32 spi_base, rcba, bcr;
|
||||
|
||||
info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->type = lpc_chipset_info[priv->chipset].spi_type;
|
||||
|
||||
switch (info->type) {
|
||||
case INTEL_SPI_BYT:
|
||||
pci_read_config_dword(dev, SPIBASE_BYT, &spi_base);
|
||||
if (spi_base & SPIBASE_BYT_EN) {
|
||||
res->start = spi_base & ~(SPIBASE_BYT_SZ - 1);
|
||||
res->end = res->start + SPIBASE_BYT_SZ - 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case INTEL_SPI_LPT:
|
||||
pci_read_config_dword(dev, RCBABASE, &rcba);
|
||||
if (rcba & 1) {
|
||||
spi_base = round_down(rcba, SPIBASE_LPT_SZ);
|
||||
res->start = spi_base + SPIBASE_LPT;
|
||||
res->end = res->start + SPIBASE_LPT_SZ - 1;
|
||||
|
||||
/*
|
||||
* Try to make the flash chip writeable now by
|
||||
* setting BCR_WPD. It it fails we tell the driver
|
||||
* that it can only read the chip.
|
||||
*/
|
||||
pci_read_config_dword(dev, BCR, &bcr);
|
||||
if (!(bcr & BCR_WPD)) {
|
||||
bcr |= BCR_WPD;
|
||||
pci_write_config_dword(dev, BCR, bcr);
|
||||
pci_read_config_dword(dev, BCR, &bcr);
|
||||
}
|
||||
info->writeable = !!(bcr & BCR_WPD);
|
||||
}
|
||||
break;
|
||||
|
||||
case INTEL_SPI_BXT: {
|
||||
unsigned int p2sb = PCI_DEVFN(13, 0);
|
||||
unsigned int spi = PCI_DEVFN(13, 2);
|
||||
struct pci_bus *bus = dev->bus;
|
||||
|
||||
/*
|
||||
* The P2SB is hidden by BIOS and we need to unhide it in
|
||||
* order to read BAR of the SPI flash device. Once that is
|
||||
* done we hide it again.
|
||||
*/
|
||||
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x0);
|
||||
pci_bus_read_config_dword(bus, spi, PCI_BASE_ADDRESS_0,
|
||||
&spi_base);
|
||||
if (spi_base != ~0) {
|
||||
res->start = spi_base & 0xfffffff0;
|
||||
res->end = res->start + SPIBASE_APL_SZ - 1;
|
||||
|
||||
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
|
||||
if (!(bcr & BCR_WPD)) {
|
||||
bcr |= BCR_WPD;
|
||||
pci_bus_write_config_dword(bus, spi, BCR, bcr);
|
||||
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
|
||||
}
|
||||
info->writeable = !!(bcr & BCR_WPD);
|
||||
}
|
||||
|
||||
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!res->start)
|
||||
return -ENODEV;
|
||||
|
||||
lpc_ich_spi_cell.platform_data = info;
|
||||
lpc_ich_spi_cell.pdata_size = sizeof(*info);
|
||||
|
||||
return mfd_add_devices(&dev->dev, PLATFORM_DEVID_NONE,
|
||||
&lpc_ich_spi_cell, 1, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int lpc_ich_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
|
@ -1099,6 +1224,12 @@ static int lpc_ich_probe(struct pci_dev *dev,
|
|||
cell_added = true;
|
||||
}
|
||||
|
||||
if (lpc_chipset_info[priv->chipset].spi_type) {
|
||||
ret = lpc_ich_init_spi(dev);
|
||||
if (!ret)
|
||||
cell_added = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only care if at least one or none of the cells registered
|
||||
* successfully.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <linux/bcm47xx_nvram.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -83,6 +84,91 @@ out_default:
|
|||
return "rootfs";
|
||||
}
|
||||
|
||||
static int bcm47xxpart_parse_trx(struct mtd_info *master,
|
||||
struct mtd_partition *trx,
|
||||
struct mtd_partition *parts,
|
||||
size_t parts_len)
|
||||
{
|
||||
struct trx_header header;
|
||||
size_t bytes_read;
|
||||
int curr_part = 0;
|
||||
int i, err;
|
||||
|
||||
if (parts_len < 3) {
|
||||
pr_warn("No enough space to add TRX partitions!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = mtd_read(master, trx->offset, sizeof(header), &bytes_read,
|
||||
(uint8_t *)&header);
|
||||
if (err && !mtd_is_bitflip(err)) {
|
||||
pr_err("mtd_read error while reading TRX header: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
/* We have LZMA loader if offset[2] points to sth */
|
||||
if (header.offset[2]) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "loader",
|
||||
trx->offset + header.offset[i], 0);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (header.offset[i]) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "linux",
|
||||
trx->offset + header.offset[i], 0);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (header.offset[i]) {
|
||||
size_t offset = trx->offset + header.offset[i];
|
||||
const char *name = bcm47xxpart_trx_data_part_name(master,
|
||||
offset);
|
||||
|
||||
bcm47xxpart_add_part(&parts[curr_part++], name, offset, 0);
|
||||
i++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assume that every partition ends at the beginning of the one it is
|
||||
* followed by.
|
||||
*/
|
||||
for (i = 0; i < curr_part; i++) {
|
||||
u64 next_part_offset = (i < curr_part - 1) ?
|
||||
parts[i + 1].offset :
|
||||
trx->offset + trx->size;
|
||||
|
||||
parts[i].size = next_part_offset - parts[i].offset;
|
||||
}
|
||||
|
||||
return curr_part;
|
||||
}
|
||||
|
||||
/**
|
||||
* bcm47xxpart_bootpartition - gets index of TRX partition used by bootloader
|
||||
*
|
||||
* Some devices may have more than one TRX partition. In such case one of them
|
||||
* is the main one and another a failsafe one. Bootloader may fallback to the
|
||||
* failsafe firmware if it detects corruption of the main image.
|
||||
*
|
||||
* This function provides info about currently used TRX partition. It's the one
|
||||
* containing kernel started by the bootloader.
|
||||
*/
|
||||
static int bcm47xxpart_bootpartition(void)
|
||||
{
|
||||
char buf[4];
|
||||
int bootpartition;
|
||||
|
||||
/* Check CFE environment variable */
|
||||
if (bcm47xx_nvram_getenv("bootpartition", buf, sizeof(buf)) > 0) {
|
||||
if (!kstrtoint(buf, 0, &bootpartition))
|
||||
return bootpartition;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm47xxpart_parse(struct mtd_info *master,
|
||||
const struct mtd_partition **pparts,
|
||||
struct mtd_part_parser_data *data)
|
||||
|
@ -93,9 +179,8 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||
size_t bytes_read;
|
||||
uint32_t offset;
|
||||
uint32_t blocksize = master->erasesize;
|
||||
struct trx_header *trx;
|
||||
int trx_part = -1;
|
||||
int last_trx_part = -1;
|
||||
int trx_parts[2]; /* Array with indexes of TRX partitions */
|
||||
int trx_num = 0; /* Number of found TRX partitions */
|
||||
int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, };
|
||||
int err;
|
||||
|
||||
|
@ -182,54 +267,18 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||
|
||||
/* TRX */
|
||||
if (buf[0x000 / 4] == TRX_MAGIC) {
|
||||
if (BCM47XXPART_MAX_PARTS - curr_part < 4) {
|
||||
pr_warn("Not enough partitions left to register trx, scanning stopped!\n");
|
||||
break;
|
||||
}
|
||||
struct trx_header *trx;
|
||||
|
||||
trx = (struct trx_header *)buf;
|
||||
|
||||
trx_part = curr_part;
|
||||
if (trx_num >= ARRAY_SIZE(trx_parts))
|
||||
pr_warn("No enough space to store another TRX found at 0x%X\n",
|
||||
offset);
|
||||
else
|
||||
trx_parts[trx_num++] = curr_part;
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "firmware",
|
||||
offset, 0);
|
||||
|
||||
i = 0;
|
||||
/* We have LZMA loader if offset[2] points to sth */
|
||||
if (trx->offset[2]) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++],
|
||||
"loader",
|
||||
offset + trx->offset[i],
|
||||
0);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (trx->offset[i]) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++],
|
||||
"linux",
|
||||
offset + trx->offset[i],
|
||||
0);
|
||||
i++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pure rootfs size is known and can be calculated as:
|
||||
* trx->length - trx->offset[i]. We don't fill it as
|
||||
* we want to have jffs2 (overlay) in the same mtd.
|
||||
*/
|
||||
if (trx->offset[i]) {
|
||||
const char *name;
|
||||
|
||||
name = bcm47xxpart_trx_data_part_name(master, offset + trx->offset[i]);
|
||||
bcm47xxpart_add_part(&parts[curr_part++],
|
||||
name,
|
||||
offset + trx->offset[i],
|
||||
0);
|
||||
i++;
|
||||
}
|
||||
|
||||
last_trx_part = curr_part - 1;
|
||||
|
||||
/* Jump to the end of TRX */
|
||||
trx = (struct trx_header *)buf;
|
||||
offset = roundup(offset + trx->length, blocksize);
|
||||
/* Next loop iteration will increase the offset */
|
||||
offset -= blocksize;
|
||||
|
@ -307,9 +356,23 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||
parts[i + 1].offset : master->size;
|
||||
|
||||
parts[i].size = next_part_offset - parts[i].offset;
|
||||
if (i == last_trx_part && trx_part >= 0)
|
||||
parts[trx_part].size = next_part_offset -
|
||||
parts[trx_part].offset;
|
||||
}
|
||||
|
||||
/* If there was TRX parse it now */
|
||||
for (i = 0; i < trx_num; i++) {
|
||||
struct mtd_partition *trx = &parts[trx_parts[i]];
|
||||
|
||||
if (i == bcm47xxpart_bootpartition()) {
|
||||
int num_parts;
|
||||
|
||||
num_parts = bcm47xxpart_parse_trx(master, trx,
|
||||
parts + curr_part,
|
||||
BCM47XXPART_MAX_PARTS - curr_part);
|
||||
if (num_parts > 0)
|
||||
curr_part += num_parts;
|
||||
} else {
|
||||
trx->name = "failsafe";
|
||||
}
|
||||
}
|
||||
|
||||
*pparts = parts;
|
||||
|
|
|
@ -105,15 +105,33 @@ static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
|||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
size_t orig_len = len;
|
||||
|
||||
/* Check address range */
|
||||
if ((from + len) > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(buf, b47s->window + from, len);
|
||||
*retlen = len;
|
||||
/* Read as much as possible using fast MMIO window */
|
||||
if (from < BCM47XXSFLASH_WINDOW_SZ) {
|
||||
size_t memcpy_len;
|
||||
|
||||
return len;
|
||||
memcpy_len = min(len, (size_t)(BCM47XXSFLASH_WINDOW_SZ - from));
|
||||
memcpy_fromio(buf, b47s->window + from, memcpy_len);
|
||||
from += memcpy_len;
|
||||
len -= memcpy_len;
|
||||
buf += memcpy_len;
|
||||
}
|
||||
|
||||
/* Use indirect access for content out of the window */
|
||||
for (; len; len--) {
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, from++);
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_READ4B);
|
||||
*buf++ = b47s->cc_read(b47s, BCMA_CC_FLASHDATA);
|
||||
}
|
||||
|
||||
*retlen = orig_len;
|
||||
|
||||
return orig_len;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len,
|
||||
|
@ -284,7 +302,6 @@ static int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
|
|||
b47s = devm_kzalloc(dev, sizeof(*b47s), GFP_KERNEL);
|
||||
if (!b47s)
|
||||
return -ENOMEM;
|
||||
sflash->priv = b47s;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
|
@ -334,6 +351,8 @@ static int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
|
|||
b47s->size = sflash->size;
|
||||
bcm47xxsflash_fill_mtd(b47s, &pdev->dev);
|
||||
|
||||
platform_set_drvdata(pdev, b47s);
|
||||
|
||||
err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0);
|
||||
if (err) {
|
||||
pr_err("Failed to register MTD device: %d\n", err);
|
||||
|
@ -349,8 +368,7 @@ static int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
|
|||
|
||||
static int bcm47xxsflash_bcma_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||
struct bcm47xxsflash *b47s = sflash->priv;
|
||||
struct bcm47xxsflash *b47s = platform_get_drvdata(pdev);
|
||||
|
||||
mtd_device_unregister(&b47s->mtd);
|
||||
iounmap(b47s->window);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
#define BCM47XXSFLASH_WINDOW_SZ SZ_16M
|
||||
|
||||
/* Used for ST flashes only. */
|
||||
#define OPCODE_ST_WREN 0x0006 /* Write Enable */
|
||||
#define OPCODE_ST_WRDIS 0x0004 /* Write Disable */
|
||||
|
@ -16,6 +18,7 @@
|
|||
#define OPCODE_ST_RES 0x03ab /* Read Electronic Signature */
|
||||
#define OPCODE_ST_CSA 0x1000 /* Keep chip select asserted */
|
||||
#define OPCODE_ST_SSE 0x0220 /* Sub-sector Erase */
|
||||
#define OPCODE_ST_READ4B 0x6313 /* Read Data Bytes in 4Byte addressing mode */
|
||||
|
||||
/* Used for Atmel flashes only. */
|
||||
#define OPCODE_AT_READ 0x07e8
|
||||
|
|
|
@ -172,7 +172,8 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
|
|||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].rx_nbits = m25p80_rx_nbits(nor);
|
||||
t[1].len = min(len, spi_max_transfer_size(spi));
|
||||
t[1].len = min3(len, spi_max_transfer_size(spi),
|
||||
spi_max_message_size(spi) - t[0].len);
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
ret = spi_sync(spi, &m);
|
||||
|
@ -288,7 +289,6 @@ static const struct spi_device_id m25p_ids[] = {
|
|||
* should be kept for backward compatibility.
|
||||
*/
|
||||
{"at25df321a"}, {"at25df641"}, {"at26df081a"},
|
||||
{"mr25h256"},
|
||||
{"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
||||
{"mx25l25635e"},{"mx66l51235l"},
|
||||
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
|
||||
|
@ -305,6 +305,11 @@ static const struct spi_device_id m25p_ids[] = {
|
|||
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
||||
{"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"},
|
||||
|
||||
/* Everspin MRAMs (non-JEDEC) */
|
||||
{ "mr25h256" }, /* 256 Kib, 40 MHz */
|
||||
{ "mr25h10" }, /* 1 Mib, 40 MHz */
|
||||
{ "mr25h40" }, /* 4 Mib, 40 MHz */
|
||||
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, m25p_ids);
|
||||
|
|
|
@ -18,19 +18,12 @@
|
|||
#define SPINOR_OP_RDVCR 0x85
|
||||
|
||||
/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */
|
||||
#define SPINOR_OP_READ_1_2_2 0xbb /* DUAL I/O READ */
|
||||
#define SPINOR_OP_READ_1_4_4 0xeb /* QUAD I/O READ */
|
||||
|
||||
#define SPINOR_OP_WRITE 0x02 /* PAGE PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT PROGRAM */
|
||||
|
||||
/* READ commands with 32-bit addressing */
|
||||
#define SPINOR_OP_READ4_1_2_2 0xbc
|
||||
#define SPINOR_OP_READ4_1_4_4 0xec
|
||||
|
||||
/* Configuration flags */
|
||||
#define FLASH_FLAG_SINGLE 0x000000ff
|
||||
#define FLASH_FLAG_READ_WRITE 0x00000001
|
||||
|
|
|
@ -507,13 +507,13 @@ static struct seq_rw_config n25q_read3_configs[] = {
|
|||
* - 'FAST' variants configured for 8 dummy cycles (see note above.)
|
||||
*/
|
||||
static struct seq_rw_config n25q_read4_configs[] = {
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -553,13 +553,13 @@ static int stfsm_mx25_en_32bit_addr_seq(struct stfsm_seq *seq)
|
|||
* entering a state that is incompatible with the SPIBoot Controller.
|
||||
*/
|
||||
static struct seq_rw_config stfsm_s25fl_read4_configs[] = {
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 2, 4},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 4, 0},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 2, 4},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 4, 0},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
};
|
||||
|
||||
static struct seq_rw_config stfsm_s25fl_write4_configs[] = {
|
||||
|
|
|
@ -75,7 +75,7 @@ config MTD_PHYSMAP_OF
|
|||
taken from OF device tree.
|
||||
|
||||
config MTD_PHYSMAP_OF_VERSATILE
|
||||
bool "Support ARM Versatile physmap OF"
|
||||
bool "ARM Versatile OF-based physical memory map handling"
|
||||
depends on MTD_PHYSMAP_OF
|
||||
depends on MFD_SYSCON
|
||||
default y if (ARCH_INTEGRATOR || ARCH_VERSATILE || ARCH_REALVIEW)
|
||||
|
@ -84,6 +84,16 @@ config MTD_PHYSMAP_OF_VERSATILE
|
|||
platforms, basically to add a VPP (write protection) callback so
|
||||
the flash can be taken out of write protection.
|
||||
|
||||
config MTD_PHYSMAP_OF_GEMINI
|
||||
bool "Cortina Gemini OF-based physical memory map handling"
|
||||
depends on MTD_PHYSMAP_OF
|
||||
depends on MFD_SYSCON
|
||||
default ARCH_GEMINI
|
||||
help
|
||||
This provides some extra DT physmap parsing for the Gemini
|
||||
platforms, some detection and setting up parallel mode on the
|
||||
external interface.
|
||||
|
||||
config MTD_PMC_MSP_EVM
|
||||
tristate "CFI Flash device mapped on PMC-Sierra MSP"
|
||||
depends on PMC_MSP && MTD_CFI
|
||||
|
|
|
@ -17,10 +17,13 @@ obj-$(CONFIG_MTD_CK804XROM) += ck804xrom.o
|
|||
obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
|
||||
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
|
||||
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
|
||||
obj-$(CONFIG_MTD_PHYSMAP_OF) += physmap_of.o
|
||||
ifdef CONFIG_MTD_PHYSMAP_OF_VERSATILE
|
||||
obj-$(CONFIG_MTD_PHYSMAP_OF) += physmap_of_versatile.o
|
||||
physmap_of-objs += physmap_of_versatile.o
|
||||
endif
|
||||
ifdef CONFIG_MTD_PHYSMAP_OF_GEMINI
|
||||
physmap_of-objs += physmap_of_gemini.o
|
||||
endif
|
||||
obj-$(CONFIG_MTD_PHYSMAP_OF) += physmap_of.o
|
||||
obj-$(CONFIG_MTD_PISMO) += pismo.o
|
||||
obj-$(CONFIG_MTD_PMC_MSP_EVM) += pmcmsp-flash.o
|
||||
obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o
|
||||
|
|
|
@ -57,10 +57,12 @@ static void ichxrom_cleanup(struct ichxrom_window *window)
|
|||
{
|
||||
struct ichxrom_map_info *map, *scratch;
|
||||
u16 word;
|
||||
int ret;
|
||||
|
||||
/* Disable writes through the rom window */
|
||||
pci_read_config_word(window->pdev, BIOS_CNTL, &word);
|
||||
pci_write_config_word(window->pdev, BIOS_CNTL, word & ~1);
|
||||
ret = pci_read_config_word(window->pdev, BIOS_CNTL, &word);
|
||||
if (!ret)
|
||||
pci_write_config_word(window->pdev, BIOS_CNTL, word & ~1);
|
||||
pci_dev_put(window->pdev);
|
||||
|
||||
/* Free all of the mtd devices */
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* by the Free Software Foundation.
|
||||
*
|
||||
* Copyright (C) 2004 Liu Peng Infineon IFAP DC COM CPE
|
||||
* Copyright (C) 2010 John Crispin <blogic@openwrt.org>
|
||||
* Copyright (C) 2010 John Crispin <john@phrozen.org>
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
@ -209,5 +209,5 @@ static struct platform_driver ltq_mtd_driver = {
|
|||
module_platform_driver(ltq_mtd_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
|
||||
MODULE_AUTHOR("John Crispin <john@phrozen.org>");
|
||||
MODULE_DESCRIPTION("Lantiq SoC NOR");
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include "physmap_of_gemini.h"
|
||||
#include "physmap_of_versatile.h"
|
||||
|
||||
struct of_flash_list {
|
||||
|
@ -241,11 +242,13 @@ static int of_flash_probe(struct platform_device *dev)
|
|||
info->list[i].map.size = res_size;
|
||||
info->list[i].map.bankwidth = be32_to_cpup(width);
|
||||
info->list[i].map.device_node = dp;
|
||||
err = of_flash_probe_versatile(dev, dp, &info->list[i].map);
|
||||
if (err) {
|
||||
dev_err(&dev->dev, "Can't probe Versatile VPP\n");
|
||||
|
||||
err = of_flash_probe_gemini(dev, dp, &info->list[i].map);
|
||||
if (err)
|
||||
return err;
|
||||
err = of_flash_probe_versatile(dev, dp, &info->list[i].map);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = -ENOMEM;
|
||||
info->list[i].map.virt = ioremap(info->list[i].map.phys,
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Cortina Systems Gemini OF physmap add-on
|
||||
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
|
||||
*
|
||||
* This SoC has an elaborate flash control register, so we need to
|
||||
* detect and set it up when booting on this platform.
|
||||
*/
|
||||
#include <linux/export.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/bitops.h>
|
||||
#include "physmap_of_gemini.h"
|
||||
|
||||
/*
|
||||
* The Flash-relevant parts of the global status register
|
||||
* These would also be relevant for a NAND driver.
|
||||
*/
|
||||
#define GLOBAL_STATUS 0x04
|
||||
#define FLASH_TYPE_MASK (0x3 << 24)
|
||||
#define FLASH_TYPE_NAND_2K (0x3 << 24)
|
||||
#define FLASH_TYPE_NAND_512 (0x2 << 24)
|
||||
#define FLASH_TYPE_PARALLEL (0x1 << 24)
|
||||
#define FLASH_TYPE_SERIAL (0x0 << 24)
|
||||
/* if parallel */
|
||||
#define FLASH_WIDTH_16BIT (1 << 23) /* else 8 bit */
|
||||
/* if serial */
|
||||
#define FLASH_ATMEL (1 << 23) /* else STM */
|
||||
|
||||
#define FLASH_SIZE_MASK (0x3 << 21)
|
||||
#define NAND_256M (0x3 << 21) /* and more */
|
||||
#define NAND_128M (0x2 << 21)
|
||||
#define NAND_64M (0x1 << 21)
|
||||
#define NAND_32M (0x0 << 21)
|
||||
#define ATMEL_16M (0x3 << 21) /* and more */
|
||||
#define ATMEL_8M (0x2 << 21)
|
||||
#define ATMEL_4M_2M (0x1 << 21)
|
||||
#define ATMEL_1M (0x0 << 21) /* and less */
|
||||
#define STM_32M (1 << 22) /* and more */
|
||||
#define STM_16M (0 << 22) /* and less */
|
||||
|
||||
#define FLASH_PARALLEL_HIGH_PIN_CNT (1 << 20) /* else low pin cnt */
|
||||
|
||||
/* Miscellaneous Control Register */
|
||||
#define GLOBAL_MISC_CTRL 0x30
|
||||
#define FLASH_PADS_MASK 0x07
|
||||
#define NAND_PADS_DISABLE BIT(2)
|
||||
#define PFLASH_PADS_DISABLE BIT(1)
|
||||
#define SFLASH_PADS_DISABLE BIT(0)
|
||||
|
||||
static const struct of_device_id syscon_match[] = {
|
||||
{ .compatible = "cortina,gemini-syscon" },
|
||||
{ },
|
||||
};
|
||||
|
||||
int of_flash_probe_gemini(struct platform_device *pdev,
|
||||
struct device_node *np,
|
||||
struct map_info *map)
|
||||
{
|
||||
static struct regmap *rmap;
|
||||
struct device *dev = &pdev->dev;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* Multiplatform guard */
|
||||
if (!of_device_is_compatible(np, "cortina,gemini-flash"))
|
||||
return 0;
|
||||
|
||||
rmap = syscon_regmap_lookup_by_phandle(np, "syscon");
|
||||
if (IS_ERR(rmap)) {
|
||||
dev_err(dev, "no syscon\n");
|
||||
return PTR_ERR(rmap);
|
||||
}
|
||||
|
||||
ret = regmap_read(rmap, GLOBAL_STATUS, &val);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to read global status register\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
dev_dbg(dev, "global status reg: %08x\n", val);
|
||||
|
||||
/*
|
||||
* It would be contradictory if a physmap flash was NOT parallel.
|
||||
*/
|
||||
if ((val & FLASH_TYPE_MASK) != FLASH_TYPE_PARALLEL) {
|
||||
dev_err(dev, "flash is not parallel\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Complain if DT data and hardware definition is different.
|
||||
*/
|
||||
if (val & FLASH_WIDTH_16BIT) {
|
||||
if (map->bankwidth != 2)
|
||||
dev_warn(dev, "flash hardware say flash is 16 bit wide but DT says it is %d bits wide\n",
|
||||
map->bankwidth * 8);
|
||||
} else {
|
||||
if (map->bankwidth != 1)
|
||||
dev_warn(dev, "flash hardware say flash is 8 bit wide but DT says it is %d bits wide\n",
|
||||
map->bankwidth * 8);
|
||||
}
|
||||
|
||||
/* Activate parallel (NOR flash) mode */
|
||||
ret = regmap_update_bits(rmap, GLOBAL_MISC_CTRL,
|
||||
FLASH_PADS_MASK,
|
||||
SFLASH_PADS_DISABLE | NAND_PADS_DISABLE);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to set up physmap pads\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "initialized Gemini-specific physmap control\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/mtd/map.h>
|
||||
|
||||
#ifdef CONFIG_MTD_PHYSMAP_OF_GEMINI
|
||||
int of_flash_probe_gemini(struct platform_device *pdev,
|
||||
struct device_node *np,
|
||||
struct map_info *map);
|
||||
#else
|
||||
static inline
|
||||
int of_flash_probe_gemini(struct platform_device *pdev,
|
||||
struct device_node *np,
|
||||
struct map_info *map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
|
@ -252,4 +252,3 @@ int of_flash_probe_versatile(struct platform_device *pdev,
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_flash_probe_versatile);
|
||||
|
|
|
@ -139,15 +139,13 @@ static int __init init_msp_flash(void)
|
|||
}
|
||||
|
||||
msp_maps[i].bankwidth = 1;
|
||||
msp_maps[i].name = kmalloc(7, GFP_KERNEL);
|
||||
msp_maps[i].name = kstrndup(flash_name, 7, GFP_KERNEL);
|
||||
if (!msp_maps[i].name) {
|
||||
iounmap(msp_maps[i].virt);
|
||||
kfree(msp_parts[i]);
|
||||
goto cleanup_loop;
|
||||
}
|
||||
|
||||
msp_maps[i].name = strncpy(msp_maps[i].name, flash_name, 7);
|
||||
|
||||
for (j = 0; j < pcnt; j++) {
|
||||
part_name[5] = '0' + i;
|
||||
part_name[7] = '0' + j;
|
||||
|
|
|
@ -451,7 +451,7 @@ static int mtdchar_readoob(struct file *file, struct mtd_info *mtd,
|
|||
* data. For our userspace tools it is important to dump areas
|
||||
* with ECC errors!
|
||||
* For kernel internal usage it also might return -EUCLEAN
|
||||
* to signal the caller that a bitflip has occured and has
|
||||
* to signal the caller that a bitflip has occurred and has
|
||||
* been corrected by the ECC algorithm.
|
||||
*
|
||||
* Note: currently the standard NAND function, nand_read_oob_std,
|
||||
|
|
|
@ -1128,7 +1128,7 @@ EXPORT_SYMBOL_GPL(mtd_write_oob);
|
|||
* @oobecc: OOB region struct filled with the appropriate ECC position
|
||||
* information
|
||||
*
|
||||
* This functions return ECC section information in the OOB area. I you want
|
||||
* This function returns ECC section information in the OOB area. If you want
|
||||
* to get all the ECC bytes information, then you should call
|
||||
* mtd_ooblayout_ecc(mtd, section++, oobecc) until it returns -ERANGE.
|
||||
*
|
||||
|
@ -1160,7 +1160,7 @@ EXPORT_SYMBOL_GPL(mtd_ooblayout_ecc);
|
|||
* @oobfree: OOB region struct filled with the appropriate free position
|
||||
* information
|
||||
*
|
||||
* This functions return free bytes position in the OOB area. I you want
|
||||
* This function returns free bytes position in the OOB area. If you want
|
||||
* to get all the free bytes information, then you should call
|
||||
* mtd_ooblayout_free(mtd, section++, oobfree) until it returns -ERANGE.
|
||||
*
|
||||
|
@ -1190,7 +1190,7 @@ EXPORT_SYMBOL_GPL(mtd_ooblayout_free);
|
|||
* @iter: iterator function. Should be either mtd_ooblayout_free or
|
||||
* mtd_ooblayout_ecc depending on the region type you're searching for
|
||||
*
|
||||
* This functions returns the section id and oobregion information of a
|
||||
* This function returns the section id and oobregion information of a
|
||||
* specific byte. For example, say you want to know where the 4th ECC byte is
|
||||
* stored, you'll use:
|
||||
*
|
||||
|
|
|
@ -349,6 +349,14 @@ static const struct mtd_ooblayout_ops part_ooblayout_ops = {
|
|||
.free = part_ooblayout_free,
|
||||
};
|
||||
|
||||
static int part_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return part->master->_max_bad_blocks(part->master,
|
||||
ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static inline void free_partition(struct mtd_part *p)
|
||||
{
|
||||
kfree(p->mtd.name);
|
||||
|
@ -424,6 +432,7 @@ static struct mtd_part *allocate_partition(struct mtd_info *master,
|
|||
slave->mtd.dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) ?
|
||||
&master->dev :
|
||||
master->dev.parent;
|
||||
slave->mtd.dev.of_node = part->of_node;
|
||||
|
||||
slave->mtd._read = part_read;
|
||||
slave->mtd._write = part_write;
|
||||
|
@ -475,6 +484,8 @@ static struct mtd_part *allocate_partition(struct mtd_info *master,
|
|||
slave->mtd._block_isbad = part_block_isbad;
|
||||
if (master->_block_markbad)
|
||||
slave->mtd._block_markbad = part_block_markbad;
|
||||
if (master->_max_bad_blocks)
|
||||
slave->mtd._max_bad_blocks = part_max_bad_blocks;
|
||||
|
||||
if (master->_get_device)
|
||||
slave->mtd._get_device = part_get_device;
|
||||
|
|
|
@ -426,6 +426,7 @@ config MTD_NAND_ORION
|
|||
|
||||
config MTD_NAND_OXNAS
|
||||
tristate "NAND Flash support for Oxford Semiconductor SoC"
|
||||
depends on ARCH_OXNAS || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables the NAND flash controller on Oxford Semiconductor SoCs.
|
||||
|
@ -535,6 +536,7 @@ config MTD_NAND_JZ4780
|
|||
|
||||
config MTD_NAND_FSMC
|
||||
tristate "Support for NAND on ST Micros FSMC"
|
||||
depends on OF
|
||||
depends on PLAT_SPEAR || ARCH_NOMADIK || ARCH_U8500 || MACH_U300
|
||||
help
|
||||
Enables support for NAND Flash chips on the ST Microelectronics
|
||||
|
|
|
@ -258,9 +258,15 @@ static void fsl_ifc_run_command(struct mtd_info *mtd)
|
|||
int bufnum = nctrl->page & priv->bufnum_mask;
|
||||
int sector = bufnum * chip->ecc.steps;
|
||||
int sector_end = sector + chip->ecc.steps - 1;
|
||||
__be32 *eccstat_regs;
|
||||
|
||||
if (ctrl->version >= FSL_IFC_VERSION_2_0_0)
|
||||
eccstat_regs = ifc->ifc_nand.v2_nand_eccstat;
|
||||
else
|
||||
eccstat_regs = ifc->ifc_nand.v1_nand_eccstat;
|
||||
|
||||
for (i = sector / 4; i <= sector_end / 4; i++)
|
||||
eccstat[i] = ifc_in32(&ifc->ifc_nand.nand_eccstat[i]);
|
||||
eccstat[i] = ifc_in32(&eccstat_regs[i]);
|
||||
|
||||
for (i = sector; i <= sector_end; i++) {
|
||||
errors = check_read_ecc(mtd, ctrl, eccstat, i);
|
||||
|
|
|
@ -35,10 +35,133 @@
|
|||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/fsmc.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <mtd/mtd-abi.h>
|
||||
|
||||
#define FSMC_NAND_BW8 1
|
||||
#define FSMC_NAND_BW16 2
|
||||
|
||||
#define FSMC_MAX_NOR_BANKS 4
|
||||
#define FSMC_MAX_NAND_BANKS 4
|
||||
|
||||
#define FSMC_FLASH_WIDTH8 1
|
||||
#define FSMC_FLASH_WIDTH16 2
|
||||
|
||||
/* fsmc controller registers for NOR flash */
|
||||
#define CTRL 0x0
|
||||
/* ctrl register definitions */
|
||||
#define BANK_ENABLE (1 << 0)
|
||||
#define MUXED (1 << 1)
|
||||
#define NOR_DEV (2 << 2)
|
||||
#define WIDTH_8 (0 << 4)
|
||||
#define WIDTH_16 (1 << 4)
|
||||
#define RSTPWRDWN (1 << 6)
|
||||
#define WPROT (1 << 7)
|
||||
#define WRT_ENABLE (1 << 12)
|
||||
#define WAIT_ENB (1 << 13)
|
||||
|
||||
#define CTRL_TIM 0x4
|
||||
/* ctrl_tim register definitions */
|
||||
|
||||
#define FSMC_NOR_BANK_SZ 0x8
|
||||
#define FSMC_NOR_REG_SIZE 0x40
|
||||
|
||||
#define FSMC_NOR_REG(base, bank, reg) (base + \
|
||||
FSMC_NOR_BANK_SZ * (bank) + \
|
||||
reg)
|
||||
|
||||
/* fsmc controller registers for NAND flash */
|
||||
#define PC 0x00
|
||||
/* pc register definitions */
|
||||
#define FSMC_RESET (1 << 0)
|
||||
#define FSMC_WAITON (1 << 1)
|
||||
#define FSMC_ENABLE (1 << 2)
|
||||
#define FSMC_DEVTYPE_NAND (1 << 3)
|
||||
#define FSMC_DEVWID_8 (0 << 4)
|
||||
#define FSMC_DEVWID_16 (1 << 4)
|
||||
#define FSMC_ECCEN (1 << 6)
|
||||
#define FSMC_ECCPLEN_512 (0 << 7)
|
||||
#define FSMC_ECCPLEN_256 (1 << 7)
|
||||
#define FSMC_TCLR_1 (1)
|
||||
#define FSMC_TCLR_SHIFT (9)
|
||||
#define FSMC_TCLR_MASK (0xF)
|
||||
#define FSMC_TAR_1 (1)
|
||||
#define FSMC_TAR_SHIFT (13)
|
||||
#define FSMC_TAR_MASK (0xF)
|
||||
#define STS 0x04
|
||||
/* sts register definitions */
|
||||
#define FSMC_CODE_RDY (1 << 15)
|
||||
#define COMM 0x08
|
||||
/* comm register definitions */
|
||||
#define FSMC_TSET_0 0
|
||||
#define FSMC_TSET_SHIFT 0
|
||||
#define FSMC_TSET_MASK 0xFF
|
||||
#define FSMC_TWAIT_6 6
|
||||
#define FSMC_TWAIT_SHIFT 8
|
||||
#define FSMC_TWAIT_MASK 0xFF
|
||||
#define FSMC_THOLD_4 4
|
||||
#define FSMC_THOLD_SHIFT 16
|
||||
#define FSMC_THOLD_MASK 0xFF
|
||||
#define FSMC_THIZ_1 1
|
||||
#define FSMC_THIZ_SHIFT 24
|
||||
#define FSMC_THIZ_MASK 0xFF
|
||||
#define ATTRIB 0x0C
|
||||
#define IOATA 0x10
|
||||
#define ECC1 0x14
|
||||
#define ECC2 0x18
|
||||
#define ECC3 0x1C
|
||||
#define FSMC_NAND_BANK_SZ 0x20
|
||||
|
||||
#define FSMC_NAND_REG(base, bank, reg) (base + FSMC_NOR_REG_SIZE + \
|
||||
(FSMC_NAND_BANK_SZ * (bank)) + \
|
||||
reg)
|
||||
|
||||
#define FSMC_BUSY_WAIT_TIMEOUT (1 * HZ)
|
||||
|
||||
struct fsmc_nand_timings {
|
||||
uint8_t tclr;
|
||||
uint8_t tar;
|
||||
uint8_t thiz;
|
||||
uint8_t thold;
|
||||
uint8_t twait;
|
||||
uint8_t tset;
|
||||
};
|
||||
|
||||
enum access_mode {
|
||||
USE_DMA_ACCESS = 1,
|
||||
USE_WORD_ACCESS,
|
||||
};
|
||||
|
||||
/**
|
||||
* fsmc_nand_platform_data - platform specific NAND controller config
|
||||
* @nand_timings: timing setup for the physical NAND interface
|
||||
* @partitions: partition table for the platform, use a default fallback
|
||||
* if this is NULL
|
||||
* @nr_partitions: the number of partitions in the previous entry
|
||||
* @options: different options for the driver
|
||||
* @width: bus width
|
||||
* @bank: default bank
|
||||
* @select_bank: callback to select a certain bank, this is
|
||||
* platform-specific. If the controller only supports one bank
|
||||
* this may be set to NULL
|
||||
*/
|
||||
struct fsmc_nand_platform_data {
|
||||
struct fsmc_nand_timings *nand_timings;
|
||||
struct mtd_partition *partitions;
|
||||
unsigned int nr_partitions;
|
||||
unsigned int options;
|
||||
unsigned int width;
|
||||
unsigned int bank;
|
||||
|
||||
enum access_mode mode;
|
||||
|
||||
void (*select_bank)(uint32_t bank, uint32_t busw);
|
||||
|
||||
/* priv structures for dma accesses */
|
||||
void *read_dma_priv;
|
||||
void *write_dma_priv;
|
||||
};
|
||||
|
||||
static int fsmc_ecc1_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobregion)
|
||||
{
|
||||
|
@ -714,7 +837,6 @@ static bool filter(struct dma_chan *chan, void *slave)
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int fsmc_nand_probe_config_dt(struct platform_device *pdev,
|
||||
struct device_node *np)
|
||||
{
|
||||
|
@ -757,13 +879,6 @@ static int fsmc_nand_probe_config_dt(struct platform_device *pdev,
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int fsmc_nand_probe_config_dt(struct platform_device *pdev,
|
||||
struct device_node *np)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* fsmc_nand_probe - Probe function
|
||||
|
@ -782,19 +897,15 @@ static int __init fsmc_nand_probe(struct platform_device *pdev)
|
|||
u32 pid;
|
||||
int i;
|
||||
|
||||
if (np) {
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
pdev->dev.platform_data = pdata;
|
||||
ret = fsmc_nand_probe_config_dt(pdev, np);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform data is NULL\n");
|
||||
return -EINVAL;
|
||||
pdev->dev.platform_data = pdata;
|
||||
ret = fsmc_nand_probe_config_dt(pdev, np);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
|
|
|
@ -797,22 +797,17 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
|
|||
struct resource *rc;
|
||||
int res;
|
||||
|
||||
rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (rc == NULL) {
|
||||
dev_err(&pdev->dev, "No memory resource found for device\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
host->io_base_dma = rc->start;
|
||||
|
||||
rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
host->io_base = devm_ioremap_resource(&pdev->dev, rc);
|
||||
if (IS_ERR(host->io_base))
|
||||
return PTR_ERR(host->io_base);
|
||||
|
||||
host->io_base_dma = rc->start;
|
||||
if (pdev->dev.of_node)
|
||||
host->ncfg = lpc32xx_parse_dt(&pdev->dev);
|
||||
if (!host->ncfg) {
|
||||
|
|
|
@ -1383,7 +1383,6 @@ static int mtk_nfc_probe(struct platform_device *pdev)
|
|||
nfc->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(nfc->regs)) {
|
||||
ret = PTR_ERR(nfc->regs);
|
||||
dev_err(dev, "no nfi base\n");
|
||||
goto release_ecc;
|
||||
}
|
||||
|
||||
|
|
|
@ -3262,6 +3262,42 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
|||
return nand_block_markbad_lowlevel(mtd, ofs);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_max_bad_blocks - [MTD Interface] Max number of bad blocks for an mtd
|
||||
* @mtd: MTD device structure
|
||||
* @ofs: offset relative to mtd start
|
||||
* @len: length of mtd
|
||||
*/
|
||||
static int nand_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
u32 part_start_block;
|
||||
u32 part_end_block;
|
||||
u32 part_start_die;
|
||||
u32 part_end_die;
|
||||
|
||||
/*
|
||||
* max_bb_per_die and blocks_per_die used to determine
|
||||
* the maximum bad block count.
|
||||
*/
|
||||
if (!chip->max_bb_per_die || !chip->blocks_per_die)
|
||||
return -ENOTSUPP;
|
||||
|
||||
/* Get the start and end of the partition in erase blocks. */
|
||||
part_start_block = mtd_div_by_eb(ofs, mtd);
|
||||
part_end_block = mtd_div_by_eb(len, mtd) + part_start_block - 1;
|
||||
|
||||
/* Get the start and end LUNs of the partition. */
|
||||
part_start_die = part_start_block / chip->blocks_per_die;
|
||||
part_end_die = part_end_block / chip->blocks_per_die;
|
||||
|
||||
/*
|
||||
* Look up the bad blocks per unit and multiply by the number of units
|
||||
* that the partition spans.
|
||||
*/
|
||||
return chip->max_bb_per_die * (part_end_die - part_start_die + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_onfi_set_features- [REPLACEABLE] set features for ONFI nand
|
||||
* @mtd: MTD device structure
|
||||
|
@ -3592,6 +3628,9 @@ static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
chip->chipsize *= (uint64_t)mtd->erasesize * p->lun_count;
|
||||
chip->bits_per_cell = p->bits_per_cell;
|
||||
|
||||
chip->max_bb_per_die = le16_to_cpu(p->bb_per_lun);
|
||||
chip->blocks_per_die = le32_to_cpu(p->blocks_per_lun);
|
||||
|
||||
if (onfi_feature(chip) & ONFI_FEATURE_16_BIT_BUS)
|
||||
*busw = NAND_BUSWIDTH_16;
|
||||
else
|
||||
|
@ -4815,6 +4854,7 @@ int nand_scan_tail(struct mtd_info *mtd)
|
|||
mtd->_block_isreserved = nand_block_isreserved;
|
||||
mtd->_block_isbad = nand_block_isbad;
|
||||
mtd->_block_markbad = nand_block_markbad;
|
||||
mtd->_max_bad_blocks = nand_max_bad_blocks;
|
||||
mtd->writebufsize = mtd->writesize;
|
||||
|
||||
/*
|
||||
|
|
|
@ -185,6 +185,7 @@ struct nand_manufacturers nand_manuf_ids[] = {
|
|||
{NAND_MFR_SANDISK, "SanDisk"},
|
||||
{NAND_MFR_INTEL, "Intel"},
|
||||
{NAND_MFR_ATO, "ATO"},
|
||||
{NAND_MFR_WINBOND, "Winbond"},
|
||||
{0x0, "Unknown"}
|
||||
};
|
||||
|
||||
|
|
|
@ -321,6 +321,10 @@ static int sunxi_nfc_wait_events(struct sunxi_nfc *nfc, u32 events,
|
|||
|
||||
ret = wait_for_completion_timeout(&nfc->complete,
|
||||
msecs_to_jiffies(timeout_ms));
|
||||
if (!ret)
|
||||
ret = -ETIMEDOUT;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
writel(0, nfc->regs + NFC_REG_INT);
|
||||
} else {
|
||||
|
@ -518,6 +522,8 @@ static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|||
u32 tmp;
|
||||
|
||||
while (len > offs) {
|
||||
bool poll = false;
|
||||
|
||||
cnt = min(len - offs, NFC_SRAM_SIZE);
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
|
@ -528,7 +534,11 @@ static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|||
tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
|
||||
writel(tmp, nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
/* Arbitrary limit for polling mode */
|
||||
if (cnt < 64)
|
||||
poll = true;
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
|
@ -551,6 +561,8 @@ static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
|
|||
u32 tmp;
|
||||
|
||||
while (len > offs) {
|
||||
bool poll = false;
|
||||
|
||||
cnt = min(len - offs, NFC_SRAM_SIZE);
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
|
@ -563,7 +575,11 @@ static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
|
|||
NFC_ACCESS_DIR;
|
||||
writel(tmp, nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
/* Arbitrary limit for polling mode */
|
||||
if (cnt < 64)
|
||||
poll = true;
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
|
@ -588,10 +604,6 @@ static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
|
|||
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
|
||||
int ret;
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
if (dat == NAND_CMD_NONE && (ctrl & NAND_NCE) &&
|
||||
!(ctrl & (NAND_CLE | NAND_ALE))) {
|
||||
u32 cmd = 0;
|
||||
|
@ -621,6 +633,10 @@ static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
|
|||
writel(sunxi_nand->addr[1],
|
||||
nfc->regs + NFC_REG_ADDR_HIGH);
|
||||
|
||||
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
writel(cmd, nfc->regs + NFC_REG_CMD);
|
||||
sunxi_nand->addr[0] = 0;
|
||||
sunxi_nand->addr[1] = 0;
|
||||
|
@ -957,7 +973,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
|
|||
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
|
||||
sunxi_nfc_randomizer_disable(mtd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -1069,7 +1085,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct mtd_info *mtd, uint8_t *buf,
|
|||
writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD | NFC_DATA_TRANS,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
|
||||
if (ret)
|
||||
dmaengine_terminate_all(nfc->dmac);
|
||||
|
||||
|
@ -1189,7 +1205,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
|
|||
NFC_ACCESS_DIR | NFC_ECC_OP,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
|
||||
sunxi_nfc_randomizer_disable(mtd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -1428,7 +1444,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct mtd_info *mtd,
|
|||
NFC_DATA_TRANS | NFC_ACCESS_DIR,
|
||||
nfc->regs + NFC_REG_CMD);
|
||||
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, true, 0);
|
||||
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
|
||||
if (ret)
|
||||
dmaengine_terminate_all(nfc->dmac);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* under the terms of the GNU General Public License version 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* Copyright © 2012 John Crispin <blogic@openwrt.org>
|
||||
* Copyright © 2012 John Crispin <john@phrozen.org>
|
||||
* Copyright © 2016 Hauke Mehrtens <hauke@hauke-m.de>
|
||||
*/
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ static int parse_ofpart_partitions(struct mtd_info *master,
|
|||
|
||||
parts[i].offset = of_read_number(reg, a_cells);
|
||||
parts[i].size = of_read_number(reg + a_cells, s_cells);
|
||||
parts[i].of_node = pp;
|
||||
|
||||
partname = of_get_property(pp, "label", &len);
|
||||
if (!partname)
|
||||
|
|
|
@ -29,6 +29,16 @@ config MTD_SPI_NOR_USE_4K_SECTORS
|
|||
Please note that some tools/drivers/filesystems may not work with
|
||||
4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
|
||||
|
||||
config SPI_ASPEED_SMC
|
||||
tristate "Aspeed flash controllers in SPI mode"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on HAS_IOMEM && OF
|
||||
help
|
||||
This enables support for the Firmware Memory controller (FMC)
|
||||
in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips,
|
||||
and support for the SPI flash memory controller (SPI) for
|
||||
the host firmware. The implementation only supports SPI NOR.
|
||||
|
||||
config SPI_ATMEL_QUADSPI
|
||||
tristate "Atmel Quad SPI Controller"
|
||||
depends on ARCH_AT91 || (ARM && COMPILE_TEST)
|
||||
|
@ -40,7 +50,7 @@ config SPI_ATMEL_QUADSPI
|
|||
|
||||
config SPI_CADENCE_QUADSPI
|
||||
tristate "Cadence Quad SPI controller"
|
||||
depends on OF && ARM
|
||||
depends on OF && (ARM || COMPILE_TEST)
|
||||
help
|
||||
Enable support for the Cadence Quad SPI Flash controller.
|
||||
|
||||
|
@ -76,4 +86,24 @@ config SPI_NXP_SPIFI
|
|||
Flash. Enable this option if you have a device with a SPIFI
|
||||
controller and want to access the Flash as a mtd device.
|
||||
|
||||
config SPI_INTEL_SPI
|
||||
tristate
|
||||
|
||||
config SPI_INTEL_SPI_PLATFORM
|
||||
tristate "Intel PCH/PCU SPI flash platform driver" if EXPERT
|
||||
depends on X86
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables platform support for the Intel PCH/PCU SPI
|
||||
controller in master mode. This controller is present in modern
|
||||
Intel hardware and is used to hold BIOS and other persistent
|
||||
settings. Using this driver it is possible to upgrade BIOS
|
||||
directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-platform.
|
||||
|
||||
endif # MTD_SPI_NOR
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
|
||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
|
||||
|
|
|
@ -0,0 +1,754 @@
|
|||
/*
|
||||
* ASPEED Static Memory Controller driver
|
||||
*
|
||||
* Copyright (c) 2015-2016, IBM Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define DEVICE_NAME "aspeed-smc"
|
||||
|
||||
/*
|
||||
* The driver only support SPI flash
|
||||
*/
|
||||
enum aspeed_smc_flash_type {
|
||||
smc_type_nor = 0,
|
||||
smc_type_nand = 1,
|
||||
smc_type_spi = 2,
|
||||
};
|
||||
|
||||
struct aspeed_smc_chip;
|
||||
|
||||
struct aspeed_smc_info {
|
||||
u32 maxsize; /* maximum size of chip window */
|
||||
u8 nce; /* number of chip enables */
|
||||
bool hastype; /* flash type field exists in config reg */
|
||||
u8 we0; /* shift for write enable bit for CE0 */
|
||||
u8 ctl0; /* offset in regs of ctl for CE0 */
|
||||
|
||||
void (*set_4b)(struct aspeed_smc_chip *chip);
|
||||
};
|
||||
|
||||
static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip);
|
||||
static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip);
|
||||
|
||||
static const struct aspeed_smc_info fmc_2400_info = {
|
||||
.maxsize = 64 * 1024 * 1024,
|
||||
.nce = 5,
|
||||
.hastype = true,
|
||||
.we0 = 16,
|
||||
.ctl0 = 0x10,
|
||||
.set_4b = aspeed_smc_chip_set_4b,
|
||||
};
|
||||
|
||||
static const struct aspeed_smc_info spi_2400_info = {
|
||||
.maxsize = 64 * 1024 * 1024,
|
||||
.nce = 1,
|
||||
.hastype = false,
|
||||
.we0 = 0,
|
||||
.ctl0 = 0x04,
|
||||
.set_4b = aspeed_smc_chip_set_4b_spi_2400,
|
||||
};
|
||||
|
||||
static const struct aspeed_smc_info fmc_2500_info = {
|
||||
.maxsize = 256 * 1024 * 1024,
|
||||
.nce = 3,
|
||||
.hastype = true,
|
||||
.we0 = 16,
|
||||
.ctl0 = 0x10,
|
||||
.set_4b = aspeed_smc_chip_set_4b,
|
||||
};
|
||||
|
||||
static const struct aspeed_smc_info spi_2500_info = {
|
||||
.maxsize = 128 * 1024 * 1024,
|
||||
.nce = 2,
|
||||
.hastype = false,
|
||||
.we0 = 16,
|
||||
.ctl0 = 0x10,
|
||||
.set_4b = aspeed_smc_chip_set_4b,
|
||||
};
|
||||
|
||||
enum aspeed_smc_ctl_reg_value {
|
||||
smc_base, /* base value without mode for other commands */
|
||||
smc_read, /* command reg for (maybe fast) reads */
|
||||
smc_write, /* command reg for writes */
|
||||
smc_max,
|
||||
};
|
||||
|
||||
struct aspeed_smc_controller;
|
||||
|
||||
struct aspeed_smc_chip {
|
||||
int cs;
|
||||
struct aspeed_smc_controller *controller;
|
||||
void __iomem *ctl; /* control register */
|
||||
void __iomem *ahb_base; /* base of chip window */
|
||||
u32 ctl_val[smc_max]; /* control settings */
|
||||
enum aspeed_smc_flash_type type; /* what type of flash */
|
||||
struct spi_nor nor;
|
||||
};
|
||||
|
||||
struct aspeed_smc_controller {
|
||||
struct device *dev;
|
||||
|
||||
struct mutex mutex; /* controller access mutex */
|
||||
const struct aspeed_smc_info *info; /* type info of controller */
|
||||
void __iomem *regs; /* controller registers */
|
||||
void __iomem *ahb_base; /* per-chip windows resource */
|
||||
|
||||
struct aspeed_smc_chip *chips[0]; /* pointers to attached chips */
|
||||
};
|
||||
|
||||
/*
|
||||
* SPI Flash Configuration Register (AST2500 SPI)
|
||||
* or
|
||||
* Type setting Register (AST2500 FMC).
|
||||
* CE0 and CE1 can only be of type SPI. CE2 can be of type NOR but the
|
||||
* driver does not support it.
|
||||
*/
|
||||
#define CONFIG_REG 0x0
|
||||
#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 */
|
||||
|
||||
#define CONFIG_CE2_WRITE BIT(18)
|
||||
#define CONFIG_CE1_WRITE BIT(17)
|
||||
#define CONFIG_CE0_WRITE BIT(16)
|
||||
|
||||
#define CONFIG_CE2_TYPE BIT(4) /* AST2500 FMC only */
|
||||
#define CONFIG_CE1_TYPE BIT(2) /* AST2500 FMC only */
|
||||
#define CONFIG_CE0_TYPE BIT(0) /* AST2500 FMC only */
|
||||
|
||||
/*
|
||||
* CE Control Register
|
||||
*/
|
||||
#define CE_CONTROL_REG 0x4
|
||||
|
||||
/*
|
||||
* CEx Control Register
|
||||
*/
|
||||
#define CONTROL_AAF_MODE BIT(31)
|
||||
#define CONTROL_IO_MODE_MASK GENMASK(30, 28)
|
||||
#define CONTROL_IO_DUAL_DATA BIT(29)
|
||||
#define CONTROL_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
|
||||
#define CONTROL_IO_QUAD_DATA BIT(30)
|
||||
#define CONTROL_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28))
|
||||
#define CONTROL_CE_INACTIVE_SHIFT 24
|
||||
#define CONTROL_CE_INACTIVE_MASK GENMASK(27, \
|
||||
CONTROL_CE_INACTIVE_SHIFT)
|
||||
/* 0 = 16T ... 15 = 1T T=HCLK */
|
||||
#define CONTROL_COMMAND_SHIFT 16
|
||||
#define CONTROL_DUMMY_COMMAND_OUT BIT(15)
|
||||
#define CONTROL_IO_DUMMY_HI BIT(14)
|
||||
#define CONTROL_IO_DUMMY_HI_SHIFT 14
|
||||
#define CONTROL_CLK_DIV4 BIT(13) /* others */
|
||||
#define CONTROL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */
|
||||
#define CONTROL_RW_MERGE BIT(12)
|
||||
#define CONTROL_IO_DUMMY_LO_SHIFT 6
|
||||
#define CONTROL_IO_DUMMY_LO GENMASK(7, \
|
||||
CONTROL_IO_DUMMY_LO_SHIFT)
|
||||
#define CONTROL_IO_DUMMY_MASK (CONTROL_IO_DUMMY_HI | \
|
||||
CONTROL_IO_DUMMY_LO)
|
||||
#define CONTROL_IO_DUMMY_SET(dummy) \
|
||||
(((((dummy) >> 2) & 0x1) << CONTROL_IO_DUMMY_HI_SHIFT) | \
|
||||
(((dummy) & 0x3) << CONTROL_IO_DUMMY_LO_SHIFT))
|
||||
|
||||
#define CONTROL_CLOCK_FREQ_SEL_SHIFT 8
|
||||
#define CONTROL_CLOCK_FREQ_SEL_MASK GENMASK(11, \
|
||||
CONTROL_CLOCK_FREQ_SEL_SHIFT)
|
||||
#define CONTROL_LSB_FIRST BIT(5)
|
||||
#define CONTROL_CLOCK_MODE_3 BIT(4)
|
||||
#define CONTROL_IN_DUAL_DATA BIT(3)
|
||||
#define CONTROL_CE_STOP_ACTIVE_CONTROL BIT(2)
|
||||
#define CONTROL_COMMAND_MODE_MASK GENMASK(1, 0)
|
||||
#define CONTROL_COMMAND_MODE_NORMAL 0
|
||||
#define CONTROL_COMMAND_MODE_FREAD 1
|
||||
#define CONTROL_COMMAND_MODE_WRITE 2
|
||||
#define CONTROL_COMMAND_MODE_USER 3
|
||||
|
||||
#define CONTROL_KEEP_MASK \
|
||||
(CONTROL_AAF_MODE | CONTROL_CE_INACTIVE_MASK | CONTROL_CLK_DIV4 | \
|
||||
CONTROL_IO_DUMMY_MASK | CONTROL_CLOCK_FREQ_SEL_MASK | \
|
||||
CONTROL_LSB_FIRST | CONTROL_CLOCK_MODE_3)
|
||||
|
||||
/*
|
||||
* The Segment Register uses a 8MB unit to encode the start address
|
||||
* and the end address of the mapping window of a flash SPI slave :
|
||||
*
|
||||
* | byte 1 | byte 2 | byte 3 | byte 4 |
|
||||
* +--------+--------+--------+--------+
|
||||
* | end | start | 0 | 0 |
|
||||
*/
|
||||
#define SEGMENT_ADDR_REG0 0x30
|
||||
#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23)
|
||||
#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23)
|
||||
|
||||
/*
|
||||
* In user mode all data bytes read or written to the chip decode address
|
||||
* range are transferred to or from the SPI bus. The range is treated as a
|
||||
* fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned
|
||||
* to its size. The address within the multiple 8kB range is ignored when
|
||||
* sending bytes to the SPI bus.
|
||||
*
|
||||
* On the arm architecture, as of Linux version 4.3, memcpy_fromio and
|
||||
* memcpy_toio on little endian targets use the optimized memcpy routines
|
||||
* that were designed for well behavied memory storage. These routines
|
||||
* have a stutter if the source and destination are not both word aligned,
|
||||
* once with a duplicate access to the source after aligning to the
|
||||
* destination to a word boundary, and again with a duplicate access to
|
||||
* the source when the final byte count is not word aligned.
|
||||
*
|
||||
* When writing or reading the fifo this stutter discards data or sends
|
||||
* too much data to the fifo and can not be used by this driver.
|
||||
*
|
||||
* While the low level io string routines that implement the insl family do
|
||||
* the desired accesses and memory increments, the cross architecture io
|
||||
* macros make them essentially impossible to use on a memory mapped address
|
||||
* instead of a a token from the call to iomap of an io port.
|
||||
*
|
||||
* These fifo routines use readl and friends to a constant io port and update
|
||||
* the memory buffer pointer and count via explicit code. The final updates
|
||||
* to len are optimistically suppressed.
|
||||
*/
|
||||
static int aspeed_smc_read_from_ahb(void *buf, void __iomem *src, size_t len)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) &&
|
||||
IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) {
|
||||
ioread32_rep(src, buf, len >> 2);
|
||||
offset = len & ~0x3;
|
||||
len -= offset;
|
||||
}
|
||||
ioread8_rep(src, (u8 *)buf + offset, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf,
|
||||
size_t len)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) &&
|
||||
IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) {
|
||||
iowrite32_rep(dst, buf, len >> 2);
|
||||
offset = len & ~0x3;
|
||||
len -= offset;
|
||||
}
|
||||
iowrite8_rep(dst, (const u8 *)buf + offset, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
return BIT(chip->controller->info->we0 + chip->cs);
|
||||
}
|
||||
|
||||
static void aspeed_smc_chip_check_config(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
u32 reg;
|
||||
|
||||
reg = readl(controller->regs + CONFIG_REG);
|
||||
|
||||
if (reg & aspeed_smc_chip_write_bit(chip))
|
||||
return;
|
||||
|
||||
dev_dbg(controller->dev, "config write is not set ! @%p: 0x%08x\n",
|
||||
controller->regs + CONFIG_REG, reg);
|
||||
reg |= aspeed_smc_chip_write_bit(chip);
|
||||
writel(reg, controller->regs + CONFIG_REG);
|
||||
}
|
||||
|
||||
static void aspeed_smc_start_user(struct spi_nor *nor)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
u32 ctl = chip->ctl_val[smc_base];
|
||||
|
||||
/*
|
||||
* When the chip is controlled in user mode, we need write
|
||||
* access to send the opcodes to it. So check the config.
|
||||
*/
|
||||
aspeed_smc_chip_check_config(chip);
|
||||
|
||||
ctl |= CONTROL_COMMAND_MODE_USER |
|
||||
CONTROL_CE_STOP_ACTIVE_CONTROL;
|
||||
writel(ctl, chip->ctl);
|
||||
|
||||
ctl &= ~CONTROL_CE_STOP_ACTIVE_CONTROL;
|
||||
writel(ctl, chip->ctl);
|
||||
}
|
||||
|
||||
static void aspeed_smc_stop_user(struct spi_nor *nor)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
u32 ctl = chip->ctl_val[smc_read];
|
||||
u32 ctl2 = ctl | CONTROL_COMMAND_MODE_USER |
|
||||
CONTROL_CE_STOP_ACTIVE_CONTROL;
|
||||
|
||||
writel(ctl2, chip->ctl); /* stop user CE control */
|
||||
writel(ctl, chip->ctl); /* default to fread or read mode */
|
||||
}
|
||||
|
||||
static int aspeed_smc_prep(struct spi_nor *nor, enum spi_nor_ops ops)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
mutex_lock(&chip->controller->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aspeed_smc_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
mutex_unlock(&chip->controller->mutex);
|
||||
}
|
||||
|
||||
static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
aspeed_smc_start_user(nor);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1);
|
||||
aspeed_smc_read_from_ahb(buf, chip->ahb_base, len);
|
||||
aspeed_smc_stop_user(nor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
int len)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
aspeed_smc_start_user(nor);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, buf, len);
|
||||
aspeed_smc_stop_user(nor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
__be32 temp;
|
||||
u32 cmdaddr;
|
||||
|
||||
switch (nor->addr_width) {
|
||||
default:
|
||||
WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
|
||||
nor->addr_width);
|
||||
/* FALLTHROUGH */
|
||||
case 3:
|
||||
cmdaddr = addr & 0xFFFFFF;
|
||||
cmdaddr |= cmd << 24;
|
||||
|
||||
temp = cpu_to_be32(cmdaddr);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4);
|
||||
break;
|
||||
case 4:
|
||||
temp = cpu_to_be32(addr);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &cmd, 1);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from,
|
||||
size_t len, u_char *read_buf)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
int i;
|
||||
u8 dummy = 0xFF;
|
||||
|
||||
aspeed_smc_start_user(nor);
|
||||
aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from);
|
||||
for (i = 0; i < chip->nor.read_dummy / 8; i++)
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy));
|
||||
|
||||
aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len);
|
||||
aspeed_smc_stop_user(nor);
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to,
|
||||
size_t len, const u_char *write_buf)
|
||||
{
|
||||
struct aspeed_smc_chip *chip = nor->priv;
|
||||
|
||||
aspeed_smc_start_user(nor);
|
||||
aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to);
|
||||
aspeed_smc_write_to_ahb(chip->ahb_base, write_buf, len);
|
||||
aspeed_smc_stop_user(nor);
|
||||
return len;
|
||||
}
|
||||
|
||||
static int aspeed_smc_unregister(struct aspeed_smc_controller *controller)
|
||||
{
|
||||
struct aspeed_smc_chip *chip;
|
||||
int n;
|
||||
|
||||
for (n = 0; n < controller->info->nce; n++) {
|
||||
chip = controller->chips[n];
|
||||
if (chip)
|
||||
mtd_device_unregister(&chip->nor.mtd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_smc_remove(struct platform_device *dev)
|
||||
{
|
||||
return aspeed_smc_unregister(platform_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static const struct of_device_id aspeed_smc_matches[] = {
|
||||
{ .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info },
|
||||
{ .compatible = "aspeed,ast2400-spi", .data = &spi_2400_info },
|
||||
{ .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info },
|
||||
{ .compatible = "aspeed,ast2500-spi", .data = &spi_2500_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, aspeed_smc_matches);
|
||||
|
||||
/*
|
||||
* Each chip has a mapping window defined by a segment address
|
||||
* register defining a start and an end address on the AHB bus. These
|
||||
* addresses can be configured to fit the chip size and offer a
|
||||
* contiguous memory region across chips. For the moment, we only
|
||||
* check that each chip segment is valid.
|
||||
*/
|
||||
static void __iomem *aspeed_smc_chip_base(struct aspeed_smc_chip *chip,
|
||||
struct resource *res)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
u32 offset = 0;
|
||||
u32 reg;
|
||||
|
||||
if (controller->info->nce > 1) {
|
||||
reg = readl(controller->regs + SEGMENT_ADDR_REG0 +
|
||||
chip->cs * 4);
|
||||
|
||||
if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg))
|
||||
return NULL;
|
||||
|
||||
offset = SEGMENT_ADDR_START(reg) - res->start;
|
||||
}
|
||||
|
||||
return controller->ahb_base + offset;
|
||||
}
|
||||
|
||||
static void aspeed_smc_chip_enable_write(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
u32 reg;
|
||||
|
||||
reg = readl(controller->regs + CONFIG_REG);
|
||||
|
||||
reg |= aspeed_smc_chip_write_bit(chip);
|
||||
writel(reg, controller->regs + CONFIG_REG);
|
||||
}
|
||||
|
||||
static void aspeed_smc_chip_set_type(struct aspeed_smc_chip *chip, int type)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
u32 reg;
|
||||
|
||||
chip->type = type;
|
||||
|
||||
reg = readl(controller->regs + CONFIG_REG);
|
||||
reg &= ~(3 << (chip->cs * 2));
|
||||
reg |= chip->type << (chip->cs * 2);
|
||||
writel(reg, controller->regs + CONFIG_REG);
|
||||
}
|
||||
|
||||
/*
|
||||
* The AST2500 FMC flash controller should be strapped by hardware, or
|
||||
* autodetected, but the AST2500 SPI flash needs to be set.
|
||||
*/
|
||||
static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
u32 reg;
|
||||
|
||||
if (chip->controller->info == &spi_2500_info) {
|
||||
reg = readl(controller->regs + CE_CONTROL_REG);
|
||||
reg |= 1 << chip->cs;
|
||||
writel(reg, controller->regs + CE_CONTROL_REG);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The AST2400 SPI flash controller does not have a CE Control
|
||||
* register. It uses the CE0 control register to set 4Byte mode at the
|
||||
* controller level.
|
||||
*/
|
||||
static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
chip->ctl_val[smc_base] |= CONTROL_IO_ADDRESS_4B;
|
||||
chip->ctl_val[smc_read] |= CONTROL_IO_ADDRESS_4B;
|
||||
}
|
||||
|
||||
static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip,
|
||||
struct resource *res)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
const struct aspeed_smc_info *info = controller->info;
|
||||
u32 reg, base_reg;
|
||||
|
||||
/*
|
||||
* Always turn on the write enable bit to allow opcodes to be
|
||||
* sent in user mode.
|
||||
*/
|
||||
aspeed_smc_chip_enable_write(chip);
|
||||
|
||||
/* The driver only supports SPI type flash */
|
||||
if (info->hastype)
|
||||
aspeed_smc_chip_set_type(chip, smc_type_spi);
|
||||
|
||||
/*
|
||||
* Configure chip base address in memory
|
||||
*/
|
||||
chip->ahb_base = aspeed_smc_chip_base(chip, res);
|
||||
if (!chip->ahb_base) {
|
||||
dev_warn(chip->nor.dev, "CE segment window closed.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get value of the inherited control register. U-Boot usually
|
||||
* does some timing calibration on the FMC chip, so it's good
|
||||
* to keep them. In the future, we should handle calibration
|
||||
* from Linux.
|
||||
*/
|
||||
reg = readl(chip->ctl);
|
||||
dev_dbg(controller->dev, "control register: %08x\n", reg);
|
||||
|
||||
base_reg = reg & CONTROL_KEEP_MASK;
|
||||
if (base_reg != reg) {
|
||||
dev_dbg(controller->dev,
|
||||
"control register changed to: %08x\n",
|
||||
base_reg);
|
||||
}
|
||||
chip->ctl_val[smc_base] = base_reg;
|
||||
|
||||
/*
|
||||
* Retain the prior value of the control register as the
|
||||
* default if it was normal access mode. Otherwise start with
|
||||
* the sanitized base value set to read mode.
|
||||
*/
|
||||
if ((reg & CONTROL_COMMAND_MODE_MASK) ==
|
||||
CONTROL_COMMAND_MODE_NORMAL)
|
||||
chip->ctl_val[smc_read] = reg;
|
||||
else
|
||||
chip->ctl_val[smc_read] = chip->ctl_val[smc_base] |
|
||||
CONTROL_COMMAND_MODE_NORMAL;
|
||||
|
||||
dev_dbg(controller->dev, "default control register: %08x\n",
|
||||
chip->ctl_val[smc_read]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
|
||||
{
|
||||
struct aspeed_smc_controller *controller = chip->controller;
|
||||
const struct aspeed_smc_info *info = controller->info;
|
||||
u32 cmd;
|
||||
|
||||
if (chip->nor.addr_width == 4 && info->set_4b)
|
||||
info->set_4b(chip);
|
||||
|
||||
/*
|
||||
* base mode has not been optimized yet. use it for writes.
|
||||
*/
|
||||
chip->ctl_val[smc_write] = chip->ctl_val[smc_base] |
|
||||
chip->nor.program_opcode << CONTROL_COMMAND_SHIFT |
|
||||
CONTROL_COMMAND_MODE_WRITE;
|
||||
|
||||
dev_dbg(controller->dev, "write control register: %08x\n",
|
||||
chip->ctl_val[smc_write]);
|
||||
|
||||
/*
|
||||
* TODO: Adjust clocks if fast read is supported and interpret
|
||||
* SPI-NOR flags to adjust controller settings.
|
||||
*/
|
||||
switch (chip->nor.flash_read) {
|
||||
case SPI_NOR_NORMAL:
|
||||
cmd = CONTROL_COMMAND_MODE_NORMAL;
|
||||
break;
|
||||
case SPI_NOR_FAST:
|
||||
cmd = CONTROL_COMMAND_MODE_FREAD;
|
||||
break;
|
||||
default:
|
||||
dev_err(chip->nor.dev, "unsupported SPI read mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chip->ctl_val[smc_read] |= cmd |
|
||||
CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8);
|
||||
|
||||
dev_dbg(controller->dev, "base control register: %08x\n",
|
||||
chip->ctl_val[smc_read]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
|
||||
struct device_node *np, struct resource *r)
|
||||
{
|
||||
const struct aspeed_smc_info *info = controller->info;
|
||||
struct device *dev = controller->dev;
|
||||
struct device_node *child;
|
||||
unsigned int cs;
|
||||
int ret = -ENODEV;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
struct aspeed_smc_chip *chip;
|
||||
struct spi_nor *nor;
|
||||
struct mtd_info *mtd;
|
||||
|
||||
/* This driver does not support NAND or NOR flash devices. */
|
||||
if (!of_device_is_compatible(child, "jedec,spi-nor"))
|
||||
continue;
|
||||
|
||||
ret = of_property_read_u32(child, "reg", &cs);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't not read chip select.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cs >= info->nce) {
|
||||
dev_err(dev, "Chip select %d out of range.\n",
|
||||
cs);
|
||||
ret = -ERANGE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (controller->chips[cs]) {
|
||||
dev_err(dev, "Chip select %d already in use by %s\n",
|
||||
cs, dev_name(controller->chips[cs]->nor.dev));
|
||||
ret = -EBUSY;
|
||||
break;
|
||||
}
|
||||
|
||||
chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
chip->controller = controller;
|
||||
chip->ctl = controller->regs + info->ctl0 + cs * 4;
|
||||
chip->cs = cs;
|
||||
|
||||
nor = &chip->nor;
|
||||
mtd = &nor->mtd;
|
||||
|
||||
nor->dev = dev;
|
||||
nor->priv = chip;
|
||||
spi_nor_set_flash_node(nor, child);
|
||||
nor->read = aspeed_smc_read_user;
|
||||
nor->write = aspeed_smc_write_user;
|
||||
nor->read_reg = aspeed_smc_read_reg;
|
||||
nor->write_reg = aspeed_smc_write_reg;
|
||||
nor->prepare = aspeed_smc_prep;
|
||||
nor->unprepare = aspeed_smc_unprep;
|
||||
|
||||
ret = aspeed_smc_chip_setup_init(chip, r);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/*
|
||||
* TODO: Add support for SPI_NOR_QUAD and SPI_NOR_DUAL
|
||||
* attach when board support is present as determined
|
||||
* by of property.
|
||||
*/
|
||||
ret = spi_nor_scan(nor, NULL, SPI_NOR_NORMAL);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = aspeed_smc_chip_setup_finish(chip);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = mtd_device_register(mtd, NULL, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
controller->chips[cs] = chip;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
aspeed_smc_unregister(controller);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aspeed_smc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct aspeed_smc_controller *controller;
|
||||
const struct of_device_id *match;
|
||||
const struct aspeed_smc_info *info;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
match = of_match_device(aspeed_smc_matches, &pdev->dev);
|
||||
if (!match || !match->data)
|
||||
return -ENODEV;
|
||||
info = match->data;
|
||||
|
||||
controller = devm_kzalloc(&pdev->dev, sizeof(*controller) +
|
||||
info->nce * sizeof(controller->chips[0]), GFP_KERNEL);
|
||||
if (!controller)
|
||||
return -ENOMEM;
|
||||
controller->info = info;
|
||||
controller->dev = dev;
|
||||
|
||||
mutex_init(&controller->mutex);
|
||||
platform_set_drvdata(pdev, controller);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
controller->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(controller->regs))
|
||||
return PTR_ERR(controller->regs);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
controller->ahb_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(controller->ahb_base))
|
||||
return PTR_ERR(controller->ahb_base);
|
||||
|
||||
ret = aspeed_smc_setup_flash(controller, np, res);
|
||||
if (ret)
|
||||
dev_err(dev, "Aspeed SMC probe failed %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver aspeed_smc_driver = {
|
||||
.probe = aspeed_smc_probe,
|
||||
.remove = aspeed_smc_remove,
|
||||
.driver = {
|
||||
.name = DEVICE_NAME,
|
||||
.of_match_table = aspeed_smc_matches,
|
||||
}
|
||||
};
|
||||
|
||||
module_platform_driver(aspeed_smc_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver");
|
||||
MODULE_AUTHOR("Cedric Le Goater <clg@kaod.org>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -526,7 +526,8 @@ static int cqspi_indirect_read_execute(struct spi_nor *nor,
|
|||
bytes_to_read *= cqspi->fifo_width;
|
||||
bytes_to_read = bytes_to_read > remaining ?
|
||||
remaining : bytes_to_read;
|
||||
readsl(ahb_base, rxbuf, DIV_ROUND_UP(bytes_to_read, 4));
|
||||
ioread32_rep(ahb_base, rxbuf,
|
||||
DIV_ROUND_UP(bytes_to_read, 4));
|
||||
rxbuf += bytes_to_read;
|
||||
remaining -= bytes_to_read;
|
||||
bytes_to_read = cqspi_get_rd_sram_level(cqspi);
|
||||
|
@ -610,7 +611,8 @@ static int cqspi_indirect_write_execute(struct spi_nor *nor,
|
|||
|
||||
while (remaining > 0) {
|
||||
write_bytes = remaining > page_size ? page_size : remaining;
|
||||
writesl(cqspi->ahb_base, txbuf, DIV_ROUND_UP(write_bytes, 4));
|
||||
iowrite32_rep(cqspi->ahb_base, txbuf,
|
||||
DIV_ROUND_UP(write_bytes, 4));
|
||||
|
||||
ret = wait_for_completion_timeout(&cqspi->transfer_complete,
|
||||
msecs_to_jiffies
|
||||
|
@ -891,7 +893,7 @@ static ssize_t cqspi_write(struct spi_nor *nor, loff_t to,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
return (ret < 0) ? ret : len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
|
||||
|
@ -911,7 +913,7 @@ static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
return (ret < 0) ? ret : len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int cqspi_erase(struct spi_nor *nor, loff_t offs)
|
||||
|
|
|
@ -193,7 +193,7 @@
|
|||
#define QUADSPI_LUT_NUM 64
|
||||
|
||||
/* SEQID -- we can have 16 seqids at most. */
|
||||
#define SEQID_QUAD_READ 0
|
||||
#define SEQID_READ 0
|
||||
#define SEQID_WREN 1
|
||||
#define SEQID_WRDI 2
|
||||
#define SEQID_RDSR 3
|
||||
|
@ -373,32 +373,26 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||
void __iomem *base = q->iobase;
|
||||
int rxfifo = q->devtype_data->rxfifo;
|
||||
u32 lut_base;
|
||||
u8 cmd, addrlen, dummy;
|
||||
int i;
|
||||
|
||||
struct spi_nor *nor = &q->nor[0];
|
||||
u8 addrlen = (nor->addr_width == 3) ? ADDR24BIT : ADDR32BIT;
|
||||
u8 read_op = nor->read_opcode;
|
||||
u8 read_dm = nor->read_dummy;
|
||||
|
||||
fsl_qspi_unlock_lut(q);
|
||||
|
||||
/* Clear all the LUT table */
|
||||
for (i = 0; i < QUADSPI_LUT_NUM; i++)
|
||||
qspi_writel(q, 0, base + QUADSPI_LUT_BASE + i * 4);
|
||||
|
||||
/* Quad Read */
|
||||
lut_base = SEQID_QUAD_READ * 4;
|
||||
/* Read */
|
||||
lut_base = SEQID_READ * 4;
|
||||
|
||||
if (q->nor_size <= SZ_16M) {
|
||||
cmd = SPINOR_OP_READ_1_1_4;
|
||||
addrlen = ADDR24BIT;
|
||||
dummy = 8;
|
||||
} else {
|
||||
/* use the 4-byte address */
|
||||
cmd = SPINOR_OP_READ_1_1_4;
|
||||
addrlen = ADDR32BIT;
|
||||
dummy = 8;
|
||||
}
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, read_op) | LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
qspi_writel(q, LUT0(DUMMY, PAD1, dummy) | LUT1(FSL_READ, PAD4, rxfifo),
|
||||
qspi_writel(q, LUT0(DUMMY, PAD1, read_dm) |
|
||||
LUT1(FSL_READ, PAD4, rxfifo),
|
||||
base + QUADSPI_LUT(lut_base + 1));
|
||||
|
||||
/* Write enable */
|
||||
|
@ -409,16 +403,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||
/* Page Program */
|
||||
lut_base = SEQID_PP * 4;
|
||||
|
||||
if (q->nor_size <= SZ_16M) {
|
||||
cmd = SPINOR_OP_PP;
|
||||
addrlen = ADDR24BIT;
|
||||
} else {
|
||||
/* use the 4-byte address */
|
||||
cmd = SPINOR_OP_PP;
|
||||
addrlen = ADDR32BIT;
|
||||
}
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, nor->program_opcode) |
|
||||
LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
qspi_writel(q, LUT0(FSL_WRITE, PAD1, 0),
|
||||
base + QUADSPI_LUT(lut_base + 1));
|
||||
|
@ -432,10 +418,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||
/* Erase a sector */
|
||||
lut_base = SEQID_SE * 4;
|
||||
|
||||
cmd = q->nor[0].erase_opcode;
|
||||
addrlen = q->nor_size <= SZ_16M ? ADDR24BIT : ADDR32BIT;
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, nor->erase_opcode) |
|
||||
LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
|
||||
/* Erase the whole chip */
|
||||
|
@ -484,7 +468,7 @@ static int fsl_qspi_get_seqid(struct fsl_qspi *q, u8 cmd)
|
|||
{
|
||||
switch (cmd) {
|
||||
case SPINOR_OP_READ_1_1_4:
|
||||
return SEQID_QUAD_READ;
|
||||
return SEQID_READ;
|
||||
case SPINOR_OP_WREN:
|
||||
return SEQID_WREN;
|
||||
case SPINOR_OP_WRDI:
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Intel PCH/PCU SPI flash platform driver.
|
||||
*
|
||||
* Copyright (C) 2016, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "intel-spi.h"
|
||||
|
||||
static int intel_spi_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct intel_spi_boardinfo *info;
|
||||
struct intel_spi *ispi;
|
||||
struct resource *mem;
|
||||
|
||||
info = dev_get_platdata(&pdev->dev);
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ispi = intel_spi_probe(&pdev->dev, mem, info);
|
||||
if (IS_ERR(ispi))
|
||||
return PTR_ERR(ispi);
|
||||
|
||||
platform_set_drvdata(pdev, ispi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_spi_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct intel_spi *ispi = platform_get_drvdata(pdev);
|
||||
|
||||
return intel_spi_remove(ispi);
|
||||
}
|
||||
|
||||
static struct platform_driver intel_spi_platform_driver = {
|
||||
.probe = intel_spi_platform_probe,
|
||||
.remove = intel_spi_platform_remove,
|
||||
.driver = {
|
||||
.name = "intel-spi",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(intel_spi_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel PCH/PCU SPI flash platform driver");
|
||||
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:intel-spi");
|
|
@ -0,0 +1,777 @@
|
|||
/*
|
||||
* Intel PCH/PCU SPI flash driver.
|
||||
*
|
||||
* Copyright (C) 2016, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/platform_data/intel-spi.h>
|
||||
|
||||
#include "intel-spi.h"
|
||||
|
||||
/* Offsets are from @ispi->base */
|
||||
#define BFPREG 0x00
|
||||
|
||||
#define HSFSTS_CTL 0x04
|
||||
#define HSFSTS_CTL_FSMIE BIT(31)
|
||||
#define HSFSTS_CTL_FDBC_SHIFT 24
|
||||
#define HSFSTS_CTL_FDBC_MASK (0x3f << HSFSTS_CTL_FDBC_SHIFT)
|
||||
|
||||
#define HSFSTS_CTL_FCYCLE_SHIFT 17
|
||||
#define HSFSTS_CTL_FCYCLE_MASK (0x0f << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
/* HW sequencer opcodes */
|
||||
#define HSFSTS_CTL_FCYCLE_READ (0x00 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_WRITE (0x02 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_ERASE (0x03 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_ERASE_64K (0x04 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_RDID (0x06 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_WRSR (0x07 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
#define HSFSTS_CTL_FCYCLE_RDSR (0x08 << HSFSTS_CTL_FCYCLE_SHIFT)
|
||||
|
||||
#define HSFSTS_CTL_FGO BIT(16)
|
||||
#define HSFSTS_CTL_FLOCKDN BIT(15)
|
||||
#define HSFSTS_CTL_FDV BIT(14)
|
||||
#define HSFSTS_CTL_SCIP BIT(5)
|
||||
#define HSFSTS_CTL_AEL BIT(2)
|
||||
#define HSFSTS_CTL_FCERR BIT(1)
|
||||
#define HSFSTS_CTL_FDONE BIT(0)
|
||||
|
||||
#define FADDR 0x08
|
||||
#define DLOCK 0x0c
|
||||
#define FDATA(n) (0x10 + ((n) * 4))
|
||||
|
||||
#define FRACC 0x50
|
||||
|
||||
#define FREG(n) (0x54 + ((n) * 4))
|
||||
#define FREG_BASE_MASK 0x3fff
|
||||
#define FREG_LIMIT_SHIFT 16
|
||||
#define FREG_LIMIT_MASK (0x03fff << FREG_LIMIT_SHIFT)
|
||||
|
||||
/* Offset is from @ispi->pregs */
|
||||
#define PR(n) ((n) * 4)
|
||||
#define PR_WPE BIT(31)
|
||||
#define PR_LIMIT_SHIFT 16
|
||||
#define PR_LIMIT_MASK (0x3fff << PR_LIMIT_SHIFT)
|
||||
#define PR_RPE BIT(15)
|
||||
#define PR_BASE_MASK 0x3fff
|
||||
/* Last PR is GPR0 */
|
||||
#define PR_NUM (5 + 1)
|
||||
|
||||
/* Offsets are from @ispi->sregs */
|
||||
#define SSFSTS_CTL 0x00
|
||||
#define SSFSTS_CTL_FSMIE BIT(23)
|
||||
#define SSFSTS_CTL_DS BIT(22)
|
||||
#define SSFSTS_CTL_DBC_SHIFT 16
|
||||
#define SSFSTS_CTL_SPOP BIT(11)
|
||||
#define SSFSTS_CTL_ACS BIT(10)
|
||||
#define SSFSTS_CTL_SCGO BIT(9)
|
||||
#define SSFSTS_CTL_COP_SHIFT 12
|
||||
#define SSFSTS_CTL_FRS BIT(7)
|
||||
#define SSFSTS_CTL_DOFRS BIT(6)
|
||||
#define SSFSTS_CTL_AEL BIT(4)
|
||||
#define SSFSTS_CTL_FCERR BIT(3)
|
||||
#define SSFSTS_CTL_FDONE BIT(2)
|
||||
#define SSFSTS_CTL_SCIP BIT(0)
|
||||
|
||||
#define PREOP_OPTYPE 0x04
|
||||
#define OPMENU0 0x08
|
||||
#define OPMENU1 0x0c
|
||||
|
||||
/* CPU specifics */
|
||||
#define BYT_PR 0x74
|
||||
#define BYT_SSFSTS_CTL 0x90
|
||||
#define BYT_BCR 0xfc
|
||||
#define BYT_BCR_WPD BIT(0)
|
||||
#define BYT_FREG_NUM 5
|
||||
|
||||
#define LPT_PR 0x74
|
||||
#define LPT_SSFSTS_CTL 0x90
|
||||
#define LPT_FREG_NUM 5
|
||||
|
||||
#define BXT_PR 0x84
|
||||
#define BXT_SSFSTS_CTL 0xa0
|
||||
#define BXT_FREG_NUM 12
|
||||
|
||||
#define INTEL_SPI_TIMEOUT 5000 /* ms */
|
||||
#define INTEL_SPI_FIFO_SZ 64
|
||||
|
||||
/**
|
||||
* struct intel_spi - Driver private data
|
||||
* @dev: Device pointer
|
||||
* @info: Pointer to board specific info
|
||||
* @nor: SPI NOR layer structure
|
||||
* @base: Beginning of MMIO space
|
||||
* @pregs: Start of protection registers
|
||||
* @sregs: Start of software sequencer registers
|
||||
* @nregions: Maximum number of regions
|
||||
* @writeable: Is the chip writeable
|
||||
* @swseq: Use SW sequencer in register reads/writes
|
||||
* @erase_64k: 64k erase supported
|
||||
* @opcodes: Opcodes which are supported. This are programmed by BIOS
|
||||
* before it locks down the controller.
|
||||
* @preopcodes: Preopcodes which are supported.
|
||||
*/
|
||||
struct intel_spi {
|
||||
struct device *dev;
|
||||
const struct intel_spi_boardinfo *info;
|
||||
struct spi_nor nor;
|
||||
void __iomem *base;
|
||||
void __iomem *pregs;
|
||||
void __iomem *sregs;
|
||||
size_t nregions;
|
||||
bool writeable;
|
||||
bool swseq;
|
||||
bool erase_64k;
|
||||
u8 opcodes[8];
|
||||
u8 preopcodes[2];
|
||||
};
|
||||
|
||||
static bool writeable;
|
||||
module_param(writeable, bool, 0);
|
||||
MODULE_PARM_DESC(writeable, "Enable write access to SPI flash chip (default=0)");
|
||||
|
||||
static void intel_spi_dump_regs(struct intel_spi *ispi)
|
||||
{
|
||||
u32 value;
|
||||
int i;
|
||||
|
||||
dev_dbg(ispi->dev, "BFPREG=0x%08x\n", readl(ispi->base + BFPREG));
|
||||
|
||||
value = readl(ispi->base + HSFSTS_CTL);
|
||||
dev_dbg(ispi->dev, "HSFSTS_CTL=0x%08x\n", value);
|
||||
if (value & HSFSTS_CTL_FLOCKDN)
|
||||
dev_dbg(ispi->dev, "-> Locked\n");
|
||||
|
||||
dev_dbg(ispi->dev, "FADDR=0x%08x\n", readl(ispi->base + FADDR));
|
||||
dev_dbg(ispi->dev, "DLOCK=0x%08x\n", readl(ispi->base + DLOCK));
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
dev_dbg(ispi->dev, "FDATA(%d)=0x%08x\n",
|
||||
i, readl(ispi->base + FDATA(i)));
|
||||
|
||||
dev_dbg(ispi->dev, "FRACC=0x%08x\n", readl(ispi->base + FRACC));
|
||||
|
||||
for (i = 0; i < ispi->nregions; i++)
|
||||
dev_dbg(ispi->dev, "FREG(%d)=0x%08x\n", i,
|
||||
readl(ispi->base + FREG(i)));
|
||||
for (i = 0; i < PR_NUM; i++)
|
||||
dev_dbg(ispi->dev, "PR(%d)=0x%08x\n", i,
|
||||
readl(ispi->pregs + PR(i)));
|
||||
|
||||
value = readl(ispi->sregs + SSFSTS_CTL);
|
||||
dev_dbg(ispi->dev, "SSFSTS_CTL=0x%08x\n", value);
|
||||
dev_dbg(ispi->dev, "PREOP_OPTYPE=0x%08x\n",
|
||||
readl(ispi->sregs + PREOP_OPTYPE));
|
||||
dev_dbg(ispi->dev, "OPMENU0=0x%08x\n", readl(ispi->sregs + OPMENU0));
|
||||
dev_dbg(ispi->dev, "OPMENU1=0x%08x\n", readl(ispi->sregs + OPMENU1));
|
||||
|
||||
if (ispi->info->type == INTEL_SPI_BYT)
|
||||
dev_dbg(ispi->dev, "BCR=0x%08x\n", readl(ispi->base + BYT_BCR));
|
||||
|
||||
dev_dbg(ispi->dev, "Protected regions:\n");
|
||||
for (i = 0; i < PR_NUM; i++) {
|
||||
u32 base, limit;
|
||||
|
||||
value = readl(ispi->pregs + PR(i));
|
||||
if (!(value & (PR_WPE | PR_RPE)))
|
||||
continue;
|
||||
|
||||
limit = (value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
|
||||
base = value & PR_BASE_MASK;
|
||||
|
||||
dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x [%c%c]\n",
|
||||
i, base << 12, (limit << 12) | 0xfff,
|
||||
value & PR_WPE ? 'W' : '.',
|
||||
value & PR_RPE ? 'R' : '.');
|
||||
}
|
||||
|
||||
dev_dbg(ispi->dev, "Flash regions:\n");
|
||||
for (i = 0; i < ispi->nregions; i++) {
|
||||
u32 region, base, limit;
|
||||
|
||||
region = readl(ispi->base + FREG(i));
|
||||
base = region & FREG_BASE_MASK;
|
||||
limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
|
||||
|
||||
if (base >= limit || (i > 0 && limit == 0))
|
||||
dev_dbg(ispi->dev, " %02d disabled\n", i);
|
||||
else
|
||||
dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x\n",
|
||||
i, base << 12, (limit << 12) | 0xfff);
|
||||
}
|
||||
|
||||
dev_dbg(ispi->dev, "Using %cW sequencer for register access\n",
|
||||
ispi->swseq ? 'S' : 'H');
|
||||
}
|
||||
|
||||
/* Reads max INTEL_SPI_FIFO_SZ bytes from the device fifo */
|
||||
static int intel_spi_read_block(struct intel_spi *ispi, void *buf, size_t size)
|
||||
{
|
||||
size_t bytes;
|
||||
int i = 0;
|
||||
|
||||
if (size > INTEL_SPI_FIFO_SZ)
|
||||
return -EINVAL;
|
||||
|
||||
while (size > 0) {
|
||||
bytes = min_t(size_t, size, 4);
|
||||
memcpy_fromio(buf, ispi->base + FDATA(i), bytes);
|
||||
size -= bytes;
|
||||
buf += bytes;
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Writes max INTEL_SPI_FIFO_SZ bytes to the device fifo */
|
||||
static int intel_spi_write_block(struct intel_spi *ispi, const void *buf,
|
||||
size_t size)
|
||||
{
|
||||
size_t bytes;
|
||||
int i = 0;
|
||||
|
||||
if (size > INTEL_SPI_FIFO_SZ)
|
||||
return -EINVAL;
|
||||
|
||||
while (size > 0) {
|
||||
bytes = min_t(size_t, size, 4);
|
||||
memcpy_toio(ispi->base + FDATA(i), buf, bytes);
|
||||
size -= bytes;
|
||||
buf += bytes;
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_spi_wait_hw_busy(struct intel_spi *ispi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(ispi->base + HSFSTS_CTL, val,
|
||||
!(val & HSFSTS_CTL_SCIP), 0,
|
||||
INTEL_SPI_TIMEOUT * 1000);
|
||||
}
|
||||
|
||||
static int intel_spi_wait_sw_busy(struct intel_spi *ispi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(ispi->sregs + SSFSTS_CTL, val,
|
||||
!(val & SSFSTS_CTL_SCIP), 0,
|
||||
INTEL_SPI_TIMEOUT * 1000);
|
||||
}
|
||||
|
||||
static int intel_spi_init(struct intel_spi *ispi)
|
||||
{
|
||||
u32 opmenu0, opmenu1, val;
|
||||
int i;
|
||||
|
||||
switch (ispi->info->type) {
|
||||
case INTEL_SPI_BYT:
|
||||
ispi->sregs = ispi->base + BYT_SSFSTS_CTL;
|
||||
ispi->pregs = ispi->base + BYT_PR;
|
||||
ispi->nregions = BYT_FREG_NUM;
|
||||
|
||||
if (writeable) {
|
||||
/* Disable write protection */
|
||||
val = readl(ispi->base + BYT_BCR);
|
||||
if (!(val & BYT_BCR_WPD)) {
|
||||
val |= BYT_BCR_WPD;
|
||||
writel(val, ispi->base + BYT_BCR);
|
||||
val = readl(ispi->base + BYT_BCR);
|
||||
}
|
||||
|
||||
ispi->writeable = !!(val & BYT_BCR_WPD);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case INTEL_SPI_LPT:
|
||||
ispi->sregs = ispi->base + LPT_SSFSTS_CTL;
|
||||
ispi->pregs = ispi->base + LPT_PR;
|
||||
ispi->nregions = LPT_FREG_NUM;
|
||||
break;
|
||||
|
||||
case INTEL_SPI_BXT:
|
||||
ispi->sregs = ispi->base + BXT_SSFSTS_CTL;
|
||||
ispi->pregs = ispi->base + BXT_PR;
|
||||
ispi->nregions = BXT_FREG_NUM;
|
||||
ispi->erase_64k = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Disable #SMI generation */
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
val &= ~HSFSTS_CTL_FSMIE;
|
||||
writel(val, ispi->base + HSFSTS_CTL);
|
||||
|
||||
/*
|
||||
* BIOS programs allowed opcodes and then locks down the register.
|
||||
* So read back what opcodes it decided to support. That's the set
|
||||
* we are going to support as well.
|
||||
*/
|
||||
opmenu0 = readl(ispi->sregs + OPMENU0);
|
||||
opmenu1 = readl(ispi->sregs + OPMENU1);
|
||||
|
||||
/*
|
||||
* Some controllers can only do basic operations using hardware
|
||||
* sequencer. All other operations are supposed to be carried out
|
||||
* using software sequencer. If we find that BIOS has programmed
|
||||
* opcodes for the software sequencer we use that over the hardware
|
||||
* sequencer.
|
||||
*/
|
||||
if (opmenu0 && opmenu1) {
|
||||
for (i = 0; i < ARRAY_SIZE(ispi->opcodes) / 2; i++) {
|
||||
ispi->opcodes[i] = opmenu0 >> i * 8;
|
||||
ispi->opcodes[i + 4] = opmenu1 >> i * 8;
|
||||
}
|
||||
|
||||
val = readl(ispi->sregs + PREOP_OPTYPE);
|
||||
ispi->preopcodes[0] = val;
|
||||
ispi->preopcodes[1] = val >> 8;
|
||||
|
||||
/* Disable #SMI generation from SW sequencer */
|
||||
val = readl(ispi->sregs + SSFSTS_CTL);
|
||||
val &= ~SSFSTS_CTL_FSMIE;
|
||||
writel(val, ispi->sregs + SSFSTS_CTL);
|
||||
|
||||
ispi->swseq = true;
|
||||
}
|
||||
|
||||
intel_spi_dump_regs(ispi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ispi->opcodes); i++)
|
||||
if (ispi->opcodes[i] == opcode)
|
||||
return i;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int intel_spi_hw_cycle(struct intel_spi *ispi, u8 opcode, u8 *buf,
|
||||
int len)
|
||||
{
|
||||
u32 val, status;
|
||||
int ret;
|
||||
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
val &= ~(HSFSTS_CTL_FCYCLE_MASK | HSFSTS_CTL_FDBC_MASK);
|
||||
|
||||
switch (opcode) {
|
||||
case SPINOR_OP_RDID:
|
||||
val |= HSFSTS_CTL_FCYCLE_RDID;
|
||||
break;
|
||||
case SPINOR_OP_WRSR:
|
||||
val |= HSFSTS_CTL_FCYCLE_WRSR;
|
||||
break;
|
||||
case SPINOR_OP_RDSR:
|
||||
val |= HSFSTS_CTL_FCYCLE_RDSR;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val |= (len - 1) << HSFSTS_CTL_FDBC_SHIFT;
|
||||
val |= HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
|
||||
val |= HSFSTS_CTL_FGO;
|
||||
writel(val, ispi->base + HSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_wait_hw_busy(ispi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
status = readl(ispi->base + HSFSTS_CTL);
|
||||
if (status & HSFSTS_CTL_FCERR)
|
||||
return -EIO;
|
||||
else if (status & HSFSTS_CTL_AEL)
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, u8 *buf,
|
||||
int len)
|
||||
{
|
||||
u32 val, status;
|
||||
int ret;
|
||||
|
||||
ret = intel_spi_opcode_index(ispi, opcode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = (len << SSFSTS_CTL_DBC_SHIFT) | SSFSTS_CTL_DS;
|
||||
val |= ret << SSFSTS_CTL_COP_SHIFT;
|
||||
val |= SSFSTS_CTL_FCERR | SSFSTS_CTL_FDONE;
|
||||
val |= SSFSTS_CTL_SCGO;
|
||||
writel(val, ispi->sregs + SSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_wait_sw_busy(ispi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
status = readl(ispi->base + SSFSTS_CTL);
|
||||
if (status & SSFSTS_CTL_FCERR)
|
||||
return -EIO;
|
||||
else if (status & SSFSTS_CTL_AEL)
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_spi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
|
||||
{
|
||||
struct intel_spi *ispi = nor->priv;
|
||||
int ret;
|
||||
|
||||
/* Address of the first chip */
|
||||
writel(0, ispi->base + FADDR);
|
||||
|
||||
if (ispi->swseq)
|
||||
ret = intel_spi_sw_cycle(ispi, opcode, buf, len);
|
||||
else
|
||||
ret = intel_spi_hw_cycle(ispi, opcode, buf, len);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return intel_spi_read_block(ispi, buf, len);
|
||||
}
|
||||
|
||||
static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
|
||||
{
|
||||
struct intel_spi *ispi = nor->priv;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This is handled with atomic operation and preop code in Intel
|
||||
* controller so skip it here now.
|
||||
*/
|
||||
if (opcode == SPINOR_OP_WREN)
|
||||
return 0;
|
||||
|
||||
writel(0, ispi->base + FADDR);
|
||||
|
||||
/* Write the value beforehand */
|
||||
ret = intel_spi_write_block(ispi, buf, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ispi->swseq)
|
||||
return intel_spi_sw_cycle(ispi, opcode, buf, len);
|
||||
return intel_spi_hw_cycle(ispi, opcode, buf, len);
|
||||
}
|
||||
|
||||
static ssize_t intel_spi_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u_char *read_buf)
|
||||
{
|
||||
struct intel_spi *ispi = nor->priv;
|
||||
size_t block_size, retlen = 0;
|
||||
u32 val, status;
|
||||
ssize_t ret;
|
||||
|
||||
switch (nor->read_opcode) {
|
||||
case SPINOR_OP_READ:
|
||||
case SPINOR_OP_READ_FAST:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ);
|
||||
|
||||
writel(from, ispi->base + FADDR);
|
||||
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
|
||||
val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
|
||||
val |= (block_size - 1) << HSFSTS_CTL_FDBC_SHIFT;
|
||||
val |= HSFSTS_CTL_FCYCLE_READ;
|
||||
val |= HSFSTS_CTL_FGO;
|
||||
writel(val, ispi->base + HSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_wait_hw_busy(ispi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
status = readl(ispi->base + HSFSTS_CTL);
|
||||
if (status & HSFSTS_CTL_FCERR)
|
||||
ret = -EIO;
|
||||
else if (status & HSFSTS_CTL_AEL)
|
||||
ret = -EACCES;
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(ispi->dev, "read error: %llx: %#x\n", from,
|
||||
status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = intel_spi_read_block(ispi, read_buf, block_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
len -= block_size;
|
||||
from += block_size;
|
||||
retlen += block_size;
|
||||
read_buf += block_size;
|
||||
}
|
||||
|
||||
return retlen;
|
||||
}
|
||||
|
||||
static ssize_t intel_spi_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
const u_char *write_buf)
|
||||
{
|
||||
struct intel_spi *ispi = nor->priv;
|
||||
size_t block_size, retlen = 0;
|
||||
u32 val, status;
|
||||
ssize_t ret;
|
||||
|
||||
while (len > 0) {
|
||||
block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ);
|
||||
|
||||
writel(to, ispi->base + FADDR);
|
||||
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
|
||||
val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
|
||||
val |= (block_size - 1) << HSFSTS_CTL_FDBC_SHIFT;
|
||||
val |= HSFSTS_CTL_FCYCLE_WRITE;
|
||||
|
||||
/* Write enable */
|
||||
if (ispi->preopcodes[1] == SPINOR_OP_WREN)
|
||||
val |= SSFSTS_CTL_SPOP;
|
||||
val |= SSFSTS_CTL_ACS;
|
||||
writel(val, ispi->base + HSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_write_block(ispi, write_buf, block_size);
|
||||
if (ret) {
|
||||
dev_err(ispi->dev, "failed to write block\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Start the write now */
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
writel(val | HSFSTS_CTL_FGO, ispi->base + HSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_wait_hw_busy(ispi);
|
||||
if (ret) {
|
||||
dev_err(ispi->dev, "timeout\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
status = readl(ispi->base + HSFSTS_CTL);
|
||||
if (status & HSFSTS_CTL_FCERR)
|
||||
ret = -EIO;
|
||||
else if (status & HSFSTS_CTL_AEL)
|
||||
ret = -EACCES;
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(ispi->dev, "write error: %llx: %#x\n", to,
|
||||
status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
len -= block_size;
|
||||
to += block_size;
|
||||
retlen += block_size;
|
||||
write_buf += block_size;
|
||||
}
|
||||
|
||||
return retlen;
|
||||
}
|
||||
|
||||
static int intel_spi_erase(struct spi_nor *nor, loff_t offs)
|
||||
{
|
||||
size_t erase_size, len = nor->mtd.erasesize;
|
||||
struct intel_spi *ispi = nor->priv;
|
||||
u32 val, status, cmd;
|
||||
int ret;
|
||||
|
||||
/* If the hardware can do 64k erase use that when possible */
|
||||
if (len >= SZ_64K && ispi->erase_64k) {
|
||||
cmd = HSFSTS_CTL_FCYCLE_ERASE_64K;
|
||||
erase_size = SZ_64K;
|
||||
} else {
|
||||
cmd = HSFSTS_CTL_FCYCLE_ERASE;
|
||||
erase_size = SZ_4K;
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
writel(offs, ispi->base + FADDR);
|
||||
|
||||
val = readl(ispi->base + HSFSTS_CTL);
|
||||
val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
|
||||
val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
|
||||
val |= cmd;
|
||||
val |= HSFSTS_CTL_FGO;
|
||||
writel(val, ispi->base + HSFSTS_CTL);
|
||||
|
||||
ret = intel_spi_wait_hw_busy(ispi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
status = readl(ispi->base + HSFSTS_CTL);
|
||||
if (status & HSFSTS_CTL_FCERR)
|
||||
return -EIO;
|
||||
else if (status & HSFSTS_CTL_AEL)
|
||||
return -EACCES;
|
||||
|
||||
offs += erase_size;
|
||||
len -= erase_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool intel_spi_is_protected(const struct intel_spi *ispi,
|
||||
unsigned int base, unsigned int limit)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PR_NUM; i++) {
|
||||
u32 pr_base, pr_limit, pr_value;
|
||||
|
||||
pr_value = readl(ispi->pregs + PR(i));
|
||||
if (!(pr_value & (PR_WPE | PR_RPE)))
|
||||
continue;
|
||||
|
||||
pr_limit = (pr_value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
|
||||
pr_base = pr_value & PR_BASE_MASK;
|
||||
|
||||
if (pr_base >= base && pr_limit <= limit)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* There will be a single partition holding all enabled flash regions. We
|
||||
* call this "BIOS".
|
||||
*/
|
||||
static void intel_spi_fill_partition(struct intel_spi *ispi,
|
||||
struct mtd_partition *part)
|
||||
{
|
||||
u64 end;
|
||||
int i;
|
||||
|
||||
memset(part, 0, sizeof(*part));
|
||||
|
||||
/* Start from the mandatory descriptor region */
|
||||
part->size = 4096;
|
||||
part->name = "BIOS";
|
||||
|
||||
/*
|
||||
* Now try to find where this partition ends based on the flash
|
||||
* region registers.
|
||||
*/
|
||||
for (i = 1; i < ispi->nregions; i++) {
|
||||
u32 region, base, limit;
|
||||
|
||||
region = readl(ispi->base + FREG(i));
|
||||
base = region & FREG_BASE_MASK;
|
||||
limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
|
||||
|
||||
if (base >= limit || limit == 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* If any of the regions have protection bits set, make the
|
||||
* whole partition read-only to be on the safe side.
|
||||
*/
|
||||
if (intel_spi_is_protected(ispi, base, limit))
|
||||
ispi->writeable = 0;
|
||||
|
||||
end = (limit << 12) + 4096;
|
||||
if (end > part->size)
|
||||
part->size = end;
|
||||
}
|
||||
}
|
||||
|
||||
struct intel_spi *intel_spi_probe(struct device *dev,
|
||||
struct resource *mem, const struct intel_spi_boardinfo *info)
|
||||
{
|
||||
struct mtd_partition part;
|
||||
struct intel_spi *ispi;
|
||||
int ret;
|
||||
|
||||
if (!info || !mem)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
ispi = devm_kzalloc(dev, sizeof(*ispi), GFP_KERNEL);
|
||||
if (!ispi)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ispi->base = devm_ioremap_resource(dev, mem);
|
||||
if (IS_ERR(ispi->base))
|
||||
return ispi->base;
|
||||
|
||||
ispi->dev = dev;
|
||||
ispi->info = info;
|
||||
ispi->writeable = info->writeable;
|
||||
|
||||
ret = intel_spi_init(ispi);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ispi->nor.dev = ispi->dev;
|
||||
ispi->nor.priv = ispi;
|
||||
ispi->nor.read_reg = intel_spi_read_reg;
|
||||
ispi->nor.write_reg = intel_spi_write_reg;
|
||||
ispi->nor.read = intel_spi_read;
|
||||
ispi->nor.write = intel_spi_write;
|
||||
ispi->nor.erase = intel_spi_erase;
|
||||
|
||||
ret = spi_nor_scan(&ispi->nor, NULL, SPI_NOR_NORMAL);
|
||||
if (ret) {
|
||||
dev_info(dev, "failed to locate the chip\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
intel_spi_fill_partition(ispi, &part);
|
||||
|
||||
/* Prevent writes if not explicitly enabled */
|
||||
if (!ispi->writeable || !writeable)
|
||||
ispi->nor.mtd.flags &= ~MTD_WRITEABLE;
|
||||
|
||||
ret = mtd_device_parse_register(&ispi->nor.mtd, NULL, NULL, &part, 1);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return ispi;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_spi_probe);
|
||||
|
||||
int intel_spi_remove(struct intel_spi *ispi)
|
||||
{
|
||||
return mtd_device_unregister(&ispi->nor.mtd);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_spi_remove);
|
||||
|
||||
MODULE_DESCRIPTION("Intel PCH/PCU SPI flash core driver");
|
||||
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Intel PCH/PCU SPI flash driver.
|
||||
*
|
||||
* Copyright (C) 2016, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef INTEL_SPI_H
|
||||
#define INTEL_SPI_H
|
||||
|
||||
#include <linux/platform_data/intel-spi.h>
|
||||
|
||||
struct intel_spi;
|
||||
struct resource;
|
||||
|
||||
struct intel_spi *intel_spi_probe(struct device *dev,
|
||||
struct resource *mem, const struct intel_spi_boardinfo *info);
|
||||
int intel_spi_remove(struct intel_spi *ispi);
|
||||
|
||||
#endif /* INTEL_SPI_H */
|
|
@ -75,6 +75,16 @@ struct flash_info {
|
|||
* bit. Must be used with
|
||||
* SPI_NOR_HAS_LOCK.
|
||||
*/
|
||||
#define SPI_S3AN BIT(10) /*
|
||||
* Xilinx Spartan 3AN In-System Flash
|
||||
* (MFR cannot be used for probing
|
||||
* because it has the same value as
|
||||
* ATMEL flashes)
|
||||
*/
|
||||
#define SPI_NOR_4B_OPCODES BIT(11) /*
|
||||
* Use dedicated 4byte address op codes
|
||||
* to support memory size above 128Mib.
|
||||
*/
|
||||
};
|
||||
|
||||
#define JEDEC_MFR(info) ((info)->id[0])
|
||||
|
@ -122,7 +132,7 @@ static int read_fsr(struct spi_nor *nor)
|
|||
/*
|
||||
* Read configuration register, returning its value in the
|
||||
* location. Return the configuration register value.
|
||||
* Returns negative if error occured.
|
||||
* Returns negative if error occurred.
|
||||
*/
|
||||
static int read_cr(struct spi_nor *nor)
|
||||
{
|
||||
|
@ -188,6 +198,78 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
|||
return mtd->priv;
|
||||
}
|
||||
|
||||
|
||||
static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
if (table[i][0] == opcode)
|
||||
return table[i][1];
|
||||
|
||||
/* No conversion found, keep input op code. */
|
||||
return opcode;
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_read(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_read[][2] = {
|
||||
{ SPINOR_OP_READ, SPINOR_OP_READ_4B },
|
||||
{ SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B },
|
||||
{ SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B },
|
||||
{ SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B },
|
||||
{ SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B },
|
||||
{ SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
|
||||
ARRAY_SIZE(spi_nor_3to4_read));
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_program(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_program[][2] = {
|
||||
{ SPINOR_OP_PP, SPINOR_OP_PP_4B },
|
||||
{ SPINOR_OP_PP_1_1_4, SPINOR_OP_PP_1_1_4_4B },
|
||||
{ SPINOR_OP_PP_1_4_4, SPINOR_OP_PP_1_4_4_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_program,
|
||||
ARRAY_SIZE(spi_nor_3to4_program));
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_erase(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_erase[][2] = {
|
||||
{ SPINOR_OP_BE_4K, SPINOR_OP_BE_4K_4B },
|
||||
{ SPINOR_OP_BE_32K, SPINOR_OP_BE_32K_4B },
|
||||
{ SPINOR_OP_SE, SPINOR_OP_SE_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase,
|
||||
ARRAY_SIZE(spi_nor_3to4_erase));
|
||||
}
|
||||
|
||||
static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
|
||||
const struct flash_info *info)
|
||||
{
|
||||
/* Do some manufacturer fixups first */
|
||||
switch (JEDEC_MFR(info)) {
|
||||
case SNOR_MFR_SPANSION:
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
nor->mtd.erasesize = info->sector_size;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
|
||||
nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
|
||||
nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
|
||||
}
|
||||
|
||||
/* Enable/disable 4-byte addressing mode. */
|
||||
static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
||||
int enable)
|
||||
|
@ -217,6 +299,21 @@ static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
|||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int s3an_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return !!(val & XSR_RDY);
|
||||
}
|
||||
|
||||
static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr = read_sr(nor);
|
||||
|
@ -238,7 +335,11 @@ static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
|||
static int spi_nor_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr, fsr;
|
||||
sr = spi_nor_sr_ready(nor);
|
||||
|
||||
if (nor->flags & SNOR_F_READY_XSR_RDY)
|
||||
sr = s3an_sr_ready(nor);
|
||||
else
|
||||
sr = spi_nor_sr_ready(nor);
|
||||
if (sr < 0)
|
||||
return sr;
|
||||
fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1;
|
||||
|
@ -319,6 +420,27 @@ static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
|
|||
mutex_unlock(&nor->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* This code converts an address to the Default Address Mode, that has non
|
||||
* power of two page sizes. We must support this mode because it is the default
|
||||
* mode supported by Xilinx tools, it can access the whole flash area and
|
||||
* changing over to the Power-of-two mode is irreversible and corrupts the
|
||||
* original data.
|
||||
* Addr can safely be unsigned int, the biggest S3AN device is smaller than
|
||||
* 4 MiB.
|
||||
*/
|
||||
static loff_t spi_nor_s3an_addr_convert(struct spi_nor *nor, unsigned int addr)
|
||||
{
|
||||
unsigned int offset;
|
||||
unsigned int page;
|
||||
|
||||
offset = addr % nor->page_size;
|
||||
page = addr / nor->page_size;
|
||||
page <<= (nor->page_size > 512) ? 10 : 9;
|
||||
|
||||
return page | offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiate the erasure of a single sector
|
||||
*/
|
||||
|
@ -327,6 +449,9 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
|
|||
u8 buf[SPI_NOR_MAX_ADDR_WIDTH];
|
||||
int i;
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
if (nor->erase)
|
||||
return nor->erase(nor, addr);
|
||||
|
||||
|
@ -368,7 +493,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|||
return ret;
|
||||
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||
unsigned long timeout;
|
||||
|
||||
write_enable(nor);
|
||||
|
@ -782,6 +907,19 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|||
.addr_width = (_addr_width), \
|
||||
.flags = (_flags),
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff \
|
||||
}, \
|
||||
.id_len = 3, \
|
||||
.sector_size = (8*_page_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = _page_size, \
|
||||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR | SPI_S3AN,
|
||||
|
||||
/* NOTE: double check command sets and memory organization when you add
|
||||
* more nor chips. This current list focusses on newer chips, which
|
||||
* have been converging on command sets which including JEDEC ID.
|
||||
|
@ -821,7 +959,7 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
{ "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
||||
/* ESMT */
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||
|
||||
/* Everspin */
|
||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
|
@ -832,6 +970,11 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
{ "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||
|
||||
/* GigaDevice */
|
||||
{
|
||||
"gd25q16", INFO(0xc84015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
"gd25q32", INFO(0xc84016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
|
@ -1014,6 +1157,13 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
{ "cat25c09", CAT25_INFO( 128, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25c17", CAT25_INFO( 256, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25128", CAT25_INFO(2048, 8, 64, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
|
||||
/* Xilinx S3AN Internal Flash */
|
||||
{ "3S50AN", S3AN_INFO(0x1f2200, 64, 264) },
|
||||
{ "3S200AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S400AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S700AN", S3AN_INFO(0x1f2500, 512, 264) },
|
||||
{ "3S1400AN", S3AN_INFO(0x1f2600, 512, 528) },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
@ -1054,7 +1204,12 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
|||
return ret;
|
||||
|
||||
while (len) {
|
||||
ret = nor->read(nor, from, len, buf);
|
||||
loff_t addr = from;
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
ret = nor->read(nor, addr, len, buf);
|
||||
if (ret == 0) {
|
||||
/* We shouldn't see 0-length reads */
|
||||
ret = -EIO;
|
||||
|
@ -1175,17 +1330,32 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||
|
||||
for (i = 0; i < len; ) {
|
||||
ssize_t written;
|
||||
loff_t addr = to + i;
|
||||
|
||||
page_offset = (to + i) & (nor->page_size - 1);
|
||||
WARN_ONCE(page_offset,
|
||||
"Writing at offset %zu into a NOR page. Writing partial pages may decrease reliability and increase wear of NOR flash.",
|
||||
page_offset);
|
||||
/*
|
||||
* If page_size is a power of two, the offset can be quickly
|
||||
* calculated with an AND operation. On the other cases we
|
||||
* need to do a modulus operation (more expensive).
|
||||
* Power of two numbers have only one bit set and we can use
|
||||
* the instruction hweight32 to detect if we need to do a
|
||||
* modulus (do_div()) or not.
|
||||
*/
|
||||
if (hweight32(nor->page_size) == 1) {
|
||||
page_offset = addr & (nor->page_size - 1);
|
||||
} else {
|
||||
uint64_t aux = addr;
|
||||
|
||||
page_offset = do_div(aux, nor->page_size);
|
||||
}
|
||||
/* the size of data remaining on the first page */
|
||||
page_remain = min_t(size_t,
|
||||
nor->page_size - page_offset, len - i);
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
write_enable(nor);
|
||||
ret = nor->write(nor, to + i, page_remain, buf + i);
|
||||
ret = nor->write(nor, addr, page_remain, buf + i);
|
||||
if (ret < 0)
|
||||
goto write_err;
|
||||
written = ret;
|
||||
|
@ -1216,6 +1386,9 @@ static int macronix_quad_enable(struct spi_nor *nor)
|
|||
val = read_sr(nor);
|
||||
if (val < 0)
|
||||
return val;
|
||||
if (val & SR_QUAD_EN_MX)
|
||||
return 0;
|
||||
|
||||
write_enable(nor);
|
||||
|
||||
write_sr(nor, val | SR_QUAD_EN_MX);
|
||||
|
@ -1236,7 +1409,7 @@ static int macronix_quad_enable(struct spi_nor *nor)
|
|||
* Write status Register and configuration register with 2 bytes
|
||||
* The first byte will be written to the status register, while the
|
||||
* second byte will be written to the configuration register.
|
||||
* Return negative if error occured.
|
||||
* Return negative if error occurred.
|
||||
*/
|
||||
static int write_sr_cr(struct spi_nor *nor, u16 val)
|
||||
{
|
||||
|
@ -1312,6 +1485,47 @@ static int spi_nor_check(struct spi_nor *nor)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
nor->erase_opcode = SPINOR_OP_XSE;
|
||||
nor->program_opcode = SPINOR_OP_XPP;
|
||||
nor->read_opcode = SPINOR_OP_READ;
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
/*
|
||||
* This flashes have a page size of 264 or 528 bytes (known as
|
||||
* Default addressing mode). It can be changed to a more standard
|
||||
* Power of two mode where the page size is 256/512. This comes
|
||||
* with a price: there is 3% less of space, the data is corrupted
|
||||
* and the page size cannot be changed back to default addressing
|
||||
* mode.
|
||||
*
|
||||
* The current addressing mode can be read from the XRDSR register
|
||||
* and should not be changed, because is a destructive operation.
|
||||
*/
|
||||
if (val & XSR_PAGESIZE) {
|
||||
/* Flash in Power of 2 mode */
|
||||
nor->page_size = (nor->page_size == 264) ? 256 : 512;
|
||||
nor->mtd.writebufsize = nor->page_size;
|
||||
nor->mtd.size = 8 * nor->page_size * info->n_sectors;
|
||||
nor->mtd.erasesize = 8 * nor->page_size;
|
||||
} else {
|
||||
/* Flash in Default addressing mode */
|
||||
nor->flags |= SNOR_F_S3AN_ADDR_DEFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
{
|
||||
const struct flash_info *info = NULL;
|
||||
|
@ -1359,6 +1573,14 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
|||
|
||||
mutex_init(&nor->lock);
|
||||
|
||||
/*
|
||||
* Make sure the XSR_RDY flag is set before calling
|
||||
* spi_nor_wait_till_ready(). Xilinx S3AN share MFR
|
||||
* with Atmel spi-nor
|
||||
*/
|
||||
if (info->flags & SPI_S3AN)
|
||||
nor->flags |= SNOR_F_READY_XSR_RDY;
|
||||
|
||||
/*
|
||||
* Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
|
||||
* with the software protection bits set
|
||||
|
@ -1483,27 +1705,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
|||
else if (mtd->size > 0x1000000) {
|
||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||
nor->addr_width = 4;
|
||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION) {
|
||||
/* Dedicated 4-byte command set */
|
||||
switch (nor->flash_read) {
|
||||
case SPI_NOR_QUAD:
|
||||
nor->read_opcode = SPINOR_OP_READ4_1_1_4;
|
||||
break;
|
||||
case SPI_NOR_DUAL:
|
||||
nor->read_opcode = SPINOR_OP_READ4_1_1_2;
|
||||
break;
|
||||
case SPI_NOR_FAST:
|
||||
nor->read_opcode = SPINOR_OP_READ4_FAST;
|
||||
break;
|
||||
case SPI_NOR_NORMAL:
|
||||
nor->read_opcode = SPINOR_OP_READ4;
|
||||
break;
|
||||
}
|
||||
nor->program_opcode = SPINOR_OP_PP_4B;
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE_4B;
|
||||
mtd->erasesize = info->sector_size;
|
||||
} else
|
||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
|
||||
info->flags & SPI_NOR_4B_OPCODES)
|
||||
spi_nor_set_4byte_opcodes(nor, info);
|
||||
else
|
||||
set_4byte(nor, info, 1);
|
||||
} else {
|
||||
nor->addr_width = 3;
|
||||
|
@ -1517,6 +1722,12 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
|||
|
||||
nor->read_dummy = spi_nor_read_dummy_cycles(nor);
|
||||
|
||||
if (info->flags & SPI_S3AN) {
|
||||
ret = s3an_nor_scan(info, nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
||||
(long long)mtd->size >> 10);
|
||||
|
||||
|
|
|
@ -371,7 +371,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
|||
/* default mode, does not need flex_cmd */
|
||||
flex_mode = 0;
|
||||
else
|
||||
command = SPINOR_OP_READ4_FAST;
|
||||
command = SPINOR_OP_READ_FAST_4B;
|
||||
break;
|
||||
case SPI_NBITS_DUAL:
|
||||
bpc = 0x00000001;
|
||||
|
@ -384,7 +384,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
|||
} else {
|
||||
command = SPINOR_OP_READ_1_1_2;
|
||||
if (spans_4byte)
|
||||
command = SPINOR_OP_READ4_1_1_2;
|
||||
command = SPINOR_OP_READ_1_1_2_4B;
|
||||
}
|
||||
break;
|
||||
case SPI_NBITS_QUAD:
|
||||
|
@ -399,7 +399,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
|||
} else {
|
||||
command = SPINOR_OP_READ_1_1_4;
|
||||
if (spans_4byte)
|
||||
command = SPINOR_OP_READ4_1_1_4;
|
||||
command = SPINOR_OP_READ_1_1_4_4B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -593,9 +593,6 @@ struct bcma_sflash {
|
|||
u32 blocksize;
|
||||
u16 numblocks;
|
||||
u32 size;
|
||||
|
||||
struct mtd_info *mtd;
|
||||
void *priv;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -733,8 +733,12 @@ struct fsl_ifc_nand {
|
|||
__be32 nand_erattr1;
|
||||
u32 res19[0x10];
|
||||
__be32 nand_fsr;
|
||||
u32 res20[0x3];
|
||||
__be32 nand_eccstat[6];
|
||||
u32 res20;
|
||||
/* The V1 nand_eccstat is actually 4 words that overlaps the
|
||||
* V2 nand_eccstat.
|
||||
*/
|
||||
__be32 v1_nand_eccstat[2];
|
||||
__be32 v2_nand_eccstat[6];
|
||||
u32 res21[0x1c];
|
||||
__be32 nanndcr;
|
||||
u32 res22[0x2];
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#ifndef LPC_ICH_H
|
||||
#define LPC_ICH_H
|
||||
|
||||
#include <linux/platform_data/intel-spi.h>
|
||||
|
||||
/* GPIO resources */
|
||||
#define ICH_RES_GPIO 0
|
||||
#define ICH_RES_GPE0 1
|
||||
|
@ -40,6 +42,7 @@ struct lpc_ich_info {
|
|||
char name[32];
|
||||
unsigned int iTCO_version;
|
||||
unsigned int gpio_version;
|
||||
enum intel_spi_type spi_type;
|
||||
u8 use_gpio;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* incude/mtd/fsmc.h
|
||||
*
|
||||
* ST Microelectronics
|
||||
* Flexible Static Memory Controller (FSMC)
|
||||
* platform data interface and header file
|
||||
*
|
||||
* Copyright © 2010 ST Microelectronics
|
||||
* Vipin Kumar <vipin.kumar@st.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __MTD_FSMC_H
|
||||
#define __MTD_FSMC_H
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mtd/physmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <asm/param.h>
|
||||
|
||||
#define FSMC_NAND_BW8 1
|
||||
#define FSMC_NAND_BW16 2
|
||||
|
||||
#define FSMC_MAX_NOR_BANKS 4
|
||||
#define FSMC_MAX_NAND_BANKS 4
|
||||
|
||||
#define FSMC_FLASH_WIDTH8 1
|
||||
#define FSMC_FLASH_WIDTH16 2
|
||||
|
||||
/* fsmc controller registers for NOR flash */
|
||||
#define CTRL 0x0
|
||||
/* ctrl register definitions */
|
||||
#define BANK_ENABLE (1 << 0)
|
||||
#define MUXED (1 << 1)
|
||||
#define NOR_DEV (2 << 2)
|
||||
#define WIDTH_8 (0 << 4)
|
||||
#define WIDTH_16 (1 << 4)
|
||||
#define RSTPWRDWN (1 << 6)
|
||||
#define WPROT (1 << 7)
|
||||
#define WRT_ENABLE (1 << 12)
|
||||
#define WAIT_ENB (1 << 13)
|
||||
|
||||
#define CTRL_TIM 0x4
|
||||
/* ctrl_tim register definitions */
|
||||
|
||||
#define FSMC_NOR_BANK_SZ 0x8
|
||||
#define FSMC_NOR_REG_SIZE 0x40
|
||||
|
||||
#define FSMC_NOR_REG(base, bank, reg) (base + \
|
||||
FSMC_NOR_BANK_SZ * (bank) + \
|
||||
reg)
|
||||
|
||||
/* fsmc controller registers for NAND flash */
|
||||
#define PC 0x00
|
||||
/* pc register definitions */
|
||||
#define FSMC_RESET (1 << 0)
|
||||
#define FSMC_WAITON (1 << 1)
|
||||
#define FSMC_ENABLE (1 << 2)
|
||||
#define FSMC_DEVTYPE_NAND (1 << 3)
|
||||
#define FSMC_DEVWID_8 (0 << 4)
|
||||
#define FSMC_DEVWID_16 (1 << 4)
|
||||
#define FSMC_ECCEN (1 << 6)
|
||||
#define FSMC_ECCPLEN_512 (0 << 7)
|
||||
#define FSMC_ECCPLEN_256 (1 << 7)
|
||||
#define FSMC_TCLR_1 (1)
|
||||
#define FSMC_TCLR_SHIFT (9)
|
||||
#define FSMC_TCLR_MASK (0xF)
|
||||
#define FSMC_TAR_1 (1)
|
||||
#define FSMC_TAR_SHIFT (13)
|
||||
#define FSMC_TAR_MASK (0xF)
|
||||
#define STS 0x04
|
||||
/* sts register definitions */
|
||||
#define FSMC_CODE_RDY (1 << 15)
|
||||
#define COMM 0x08
|
||||
/* comm register definitions */
|
||||
#define FSMC_TSET_0 0
|
||||
#define FSMC_TSET_SHIFT 0
|
||||
#define FSMC_TSET_MASK 0xFF
|
||||
#define FSMC_TWAIT_6 6
|
||||
#define FSMC_TWAIT_SHIFT 8
|
||||
#define FSMC_TWAIT_MASK 0xFF
|
||||
#define FSMC_THOLD_4 4
|
||||
#define FSMC_THOLD_SHIFT 16
|
||||
#define FSMC_THOLD_MASK 0xFF
|
||||
#define FSMC_THIZ_1 1
|
||||
#define FSMC_THIZ_SHIFT 24
|
||||
#define FSMC_THIZ_MASK 0xFF
|
||||
#define ATTRIB 0x0C
|
||||
#define IOATA 0x10
|
||||
#define ECC1 0x14
|
||||
#define ECC2 0x18
|
||||
#define ECC3 0x1C
|
||||
#define FSMC_NAND_BANK_SZ 0x20
|
||||
|
||||
#define FSMC_NAND_REG(base, bank, reg) (base + FSMC_NOR_REG_SIZE + \
|
||||
(FSMC_NAND_BANK_SZ * (bank)) + \
|
||||
reg)
|
||||
|
||||
#define FSMC_BUSY_WAIT_TIMEOUT (1 * HZ)
|
||||
|
||||
struct fsmc_nand_timings {
|
||||
uint8_t tclr;
|
||||
uint8_t tar;
|
||||
uint8_t thiz;
|
||||
uint8_t thold;
|
||||
uint8_t twait;
|
||||
uint8_t tset;
|
||||
};
|
||||
|
||||
enum access_mode {
|
||||
USE_DMA_ACCESS = 1,
|
||||
USE_WORD_ACCESS,
|
||||
};
|
||||
|
||||
/**
|
||||
* fsmc_nand_platform_data - platform specific NAND controller config
|
||||
* @nand_timings: timing setup for the physical NAND interface
|
||||
* @partitions: partition table for the platform, use a default fallback
|
||||
* if this is NULL
|
||||
* @nr_partitions: the number of partitions in the previous entry
|
||||
* @options: different options for the driver
|
||||
* @width: bus width
|
||||
* @bank: default bank
|
||||
* @select_bank: callback to select a certain bank, this is
|
||||
* platform-specific. If the controller only supports one bank
|
||||
* this may be set to NULL
|
||||
*/
|
||||
struct fsmc_nand_platform_data {
|
||||
struct fsmc_nand_timings *nand_timings;
|
||||
struct mtd_partition *partitions;
|
||||
unsigned int nr_partitions;
|
||||
unsigned int options;
|
||||
unsigned int width;
|
||||
unsigned int bank;
|
||||
|
||||
enum access_mode mode;
|
||||
|
||||
void (*select_bank)(uint32_t bank, uint32_t busw);
|
||||
|
||||
/* priv structures for dma accesses */
|
||||
void *read_dma_priv;
|
||||
void *write_dma_priv;
|
||||
};
|
||||
|
||||
extern int __init fsmc_nor_init(struct platform_device *pdev,
|
||||
unsigned long base, uint32_t bank, uint32_t width);
|
||||
extern void __init fsmc_init_board_info(struct platform_device *pdev,
|
||||
struct mtd_partition *partitions, unsigned int nr_partitions,
|
||||
unsigned int width);
|
||||
|
||||
#endif /* __MTD_FSMC_H */
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/uio.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <mtd/mtd-abi.h>
|
||||
|
||||
|
@ -322,6 +323,7 @@ struct mtd_info {
|
|||
int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
|
||||
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
|
||||
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
|
||||
int (*_max_bad_blocks) (struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
int (*_suspend) (struct mtd_info *mtd);
|
||||
void (*_resume) (struct mtd_info *mtd);
|
||||
void (*_reboot) (struct mtd_info *mtd);
|
||||
|
@ -385,6 +387,8 @@ static inline void mtd_set_of_node(struct mtd_info *mtd,
|
|||
struct device_node *np)
|
||||
{
|
||||
mtd->dev.of_node = np;
|
||||
if (!mtd->name)
|
||||
of_property_read_string(np, "label", &mtd->name);
|
||||
}
|
||||
|
||||
static inline struct device_node *mtd_get_of_node(struct mtd_info *mtd)
|
||||
|
@ -397,6 +401,18 @@ static inline int mtd_oobavail(struct mtd_info *mtd, struct mtd_oob_ops *ops)
|
|||
return ops->mode == MTD_OPS_AUTO_OOB ? mtd->oobavail : mtd->oobsize;
|
||||
}
|
||||
|
||||
static inline int mtd_max_bad_blocks(struct mtd_info *mtd,
|
||||
loff_t ofs, size_t len)
|
||||
{
|
||||
if (!mtd->_max_bad_blocks)
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (mtd->size < (len + ofs) || ofs < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return mtd->_max_bad_blocks(mtd, ofs, len);
|
||||
}
|
||||
|
||||
int mtd_wunit_to_pairing_info(struct mtd_info *mtd, int wunit,
|
||||
struct mtd_pairing_info *info);
|
||||
int mtd_pairing_info_to_wunit(struct mtd_info *mtd,
|
||||
|
|
|
@ -615,7 +615,7 @@ struct nand_buffers {
|
|||
* @tALS_min: ALE setup time
|
||||
* @tAR_min: ALE to RE# delay
|
||||
* @tCEA_max: CE# access time
|
||||
* @tCEH_min:
|
||||
* @tCEH_min: CE# high hold time
|
||||
* @tCH_min: CE# hold time
|
||||
* @tCHZ_max: CE# high to output hi-Z
|
||||
* @tCLH_min: CLE hold time
|
||||
|
@ -801,6 +801,10 @@ nand_get_sdr_timings(const struct nand_data_interface *conf)
|
|||
* supported, 0 otherwise.
|
||||
* @jedec_params: [INTERN] holds the JEDEC parameter page when JEDEC is
|
||||
* supported, 0 otherwise.
|
||||
* @max_bb_per_die: [INTERN] the max number of bad blocks each die of a
|
||||
* this nand device will encounter their life times.
|
||||
* @blocks_per_die: [INTERN] The number of PEBs in a die
|
||||
* @data_interface: [INTERN] NAND interface timing information
|
||||
* @read_retries: [INTERN] the number of read retry modes supported
|
||||
* @onfi_set_features: [REPLACEABLE] set the features for ONFI nand
|
||||
* @onfi_get_features: [REPLACEABLE] get the features for ONFI nand
|
||||
|
@ -883,6 +887,8 @@ struct nand_chip {
|
|||
struct nand_onfi_params onfi_params;
|
||||
struct nand_jedec_params jedec_params;
|
||||
};
|
||||
u16 max_bb_per_die;
|
||||
u32 blocks_per_die;
|
||||
|
||||
struct nand_data_interface *data_interface;
|
||||
|
||||
|
@ -958,6 +964,7 @@ static inline void nand_set_controller_data(struct nand_chip *chip, void *priv)
|
|||
#define NAND_MFR_SANDISK 0x45
|
||||
#define NAND_MFR_INTEL 0x89
|
||||
#define NAND_MFR_ATO 0x9b
|
||||
#define NAND_MFR_WINBOND 0xef
|
||||
|
||||
/* The maximum expected count of bytes in the NAND ID sequence */
|
||||
#define NAND_MAX_ID_LEN 8
|
||||
|
|
|
@ -41,6 +41,7 @@ struct mtd_partition {
|
|||
uint64_t size; /* partition size */
|
||||
uint64_t offset; /* offset within the master MTD space */
|
||||
uint32_t mask_flags; /* master MTD flags to mask out for this partition */
|
||||
struct device_node *of_node;
|
||||
};
|
||||
|
||||
#define MTDPART_OFS_RETAIN (-3)
|
||||
|
|
|
@ -43,9 +43,13 @@
|
|||
#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */
|
||||
#define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad SPI) */
|
||||
#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual Output SPI) */
|
||||
#define SPINOR_OP_READ_1_2_2 0xbb /* Read data bytes (Dual I/O SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad Output SPI) */
|
||||
#define SPINOR_OP_READ_1_4_4 0xeb /* Read data bytes (Quad I/O SPI) */
|
||||
#define SPINOR_OP_PP 0x02 /* Page program (up to 256 bytes) */
|
||||
#define SPINOR_OP_PP_1_1_4 0x32 /* Quad page program */
|
||||
#define SPINOR_OP_PP_1_4_4 0x38 /* Quad page program */
|
||||
#define SPINOR_OP_BE_4K 0x20 /* Erase 4KiB block */
|
||||
#define SPINOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */
|
||||
#define SPINOR_OP_BE_32K 0x52 /* Erase 32KiB block */
|
||||
|
@ -56,11 +60,17 @@
|
|||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||
|
||||
/* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
|
||||
#define SPINOR_OP_READ4 0x13 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ4_FAST 0x0c /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ4_1_1_2 0x3c /* Read data bytes (Dual SPI) */
|
||||
#define SPINOR_OP_READ4_1_1_4 0x6c /* Read data bytes (Quad SPI) */
|
||||
#define SPINOR_OP_READ_4B 0x13 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ_FAST_4B 0x0c /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ_1_1_2_4B 0x3c /* Read data bytes (Dual Output SPI) */
|
||||
#define SPINOR_OP_READ_1_2_2_4B 0xbc /* Read data bytes (Dual I/O SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4_4B 0x6c /* Read data bytes (Quad Output SPI) */
|
||||
#define SPINOR_OP_READ_1_4_4_4B 0xec /* Read data bytes (Quad I/O SPI) */
|
||||
#define SPINOR_OP_PP_4B 0x12 /* Page program (up to 256 bytes) */
|
||||
#define SPINOR_OP_PP_1_1_4_4B 0x34 /* Quad page program */
|
||||
#define SPINOR_OP_PP_1_4_4_4B 0x3e /* Quad page program */
|
||||
#define SPINOR_OP_BE_4K_4B 0x21 /* Erase 4KiB block */
|
||||
#define SPINOR_OP_BE_32K_4B 0x5c /* Erase 32KiB block */
|
||||
#define SPINOR_OP_SE_4B 0xdc /* Sector erase (usually 64KiB) */
|
||||
|
||||
/* Used for SST flashes only. */
|
||||
|
@ -68,6 +78,15 @@
|
|||
#define SPINOR_OP_WRDI 0x04 /* Write disable */
|
||||
#define SPINOR_OP_AAI_WP 0xad /* Auto address increment word program */
|
||||
|
||||
/* Used for S3AN flashes only */
|
||||
#define SPINOR_OP_XSE 0x50 /* Sector erase */
|
||||
#define SPINOR_OP_XPP 0x82 /* Page program */
|
||||
#define SPINOR_OP_XRDSR 0xd7 /* Read status register */
|
||||
|
||||
#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */
|
||||
#define XSR_RDY BIT(7) /* Ready */
|
||||
|
||||
|
||||
/* Used for Macronix and Winbond flashes. */
|
||||
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
|
||||
#define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
|
||||
|
@ -119,6 +138,9 @@ enum spi_nor_ops {
|
|||
enum spi_nor_option_flags {
|
||||
SNOR_F_USE_FSR = BIT(0),
|
||||
SNOR_F_HAS_SR_TB = BIT(1),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||
SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
|
||||
SNOR_F_READY_XSR_RDY = BIT(4),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Intel PCH/PCU SPI flash driver.
|
||||
*
|
||||
* Copyright (C) 2016, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef INTEL_SPI_PDATA_H
|
||||
#define INTEL_SPI_PDATA_H
|
||||
|
||||
enum intel_spi_type {
|
||||
INTEL_SPI_BYT = 1,
|
||||
INTEL_SPI_LPT,
|
||||
INTEL_SPI_BXT,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct intel_spi_boardinfo - Board specific data for Intel SPI driver
|
||||
* @type: Type which this controller is compatible with
|
||||
* @writeable: The chip is writeable
|
||||
*/
|
||||
struct intel_spi_boardinfo {
|
||||
enum intel_spi_type type;
|
||||
bool writeable;
|
||||
};
|
||||
|
||||
#endif /* INTEL_SPI_PDATA_H */
|
Loading…
Reference in New Issue