This pull request contains the following notable changes:
- add support to the 4-byte address instruction set. - add support to new memory parts. - add support to S3AN memories. - add support to the Intel SPI controller. - add support to the Aspeed AST2400 and AST2550 controllers. - fix max SPI transfer and message sizes in m25p80_read(). - fix the Candence QSPI driver. - fix the Freescale QSPI driver. -----BEGIN PGP SIGNATURE----- iQI4BAABCAAiBQJYncJ7GxxjeXJpbGxlLnBpdGNoZW5Ad2VkZXY0dS5mcgAKCRDn 4OgLHRpJcswfD/0dvIvhI9R9ne/kHUsXbW2tIAIc2vrl/jiEA0O90rinRRjxLyIi 3EacSYpL8PXAh5diNannIw/DrjK0FiHB34jJ1NwMP7CQbYMbfb1SGwp5nmIP676W 4iEaSOJDQu76Vbe5L2O4xHUIJoO1l9cr/bZDPXRachBD8H48ZRZUTx0ujff4LbiG lEwv1Bk35doTRHgmPErPIB/WOlYdvLaoiCUVe9ME0mEEzCmEjoZorTDwaSBpBAMB 4Z04Lz06iP1aPFUm2VYvK5xMvkFN4tS+6xzWpUOUYj8Tty1YptYI7mQINa69J2NA O8A/elH39Yv1FYvWgrC2SIS/kbHTR/Bxz6napJ0hK3HasLFCwt7Eq2w/XBxr/Wn9 +02buij0PWKywJfuj7tOiAId5IJW0hhEit2BcBFihwsiDDxix679beQoCZ/WRuW/ kJmR+NLRNByVyKCOP239VobiYWjy8DhDTpH5XGwI7bPTiJuS6G6MCVH3f0tYnFTo 8J3gXp4SH2mw6YzWrL8YtKzFKcLEfXTOGc/GFGQX2URV5Es1/Lw4Nh5lrkbEbVoy dc/S57Ftdde/56L4lDDbydn7pAU8FbuX2gSewvJRuFyhCBKSyw2JEoy27T7t50pd hN1JvuNYGQ5HP0Vqa/oe1g1GHnBfxiIOm5THX+Iv+3Y0jM1iUDeVeO4DTQ== =/fPM -----END PGP SIGNATURE----- Merge tag 'spi-nor/for-4.11-v2' of git://github.com/spi-nor/linux From Cyrille: """ This pull request contains the following notable changes: - add support to the 4-byte address instruction set. - add support to new memory parts. - add support to S3AN memories. - add support to the Intel SPI controller. - add support to the Aspeed AST2400 and AST2550 controllers. - fix max SPI transfer and message sizes in m25p80_read(). - fix the Candence QSPI driver. - fix the Freescale QSPI driver. """
This commit is contained in:
commit
398d8739bb
51
Documentation/devicetree/bindings/mtd/aspeed-smc.txt
Normal file
51
Documentation/devicetree/bindings/mtd/aspeed-smc.txt
Normal file
@ -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>;
|
||||||
|
};
|
||||||
|
};
|
@ -14,6 +14,8 @@ Required properties:
|
|||||||
at25df641
|
at25df641
|
||||||
at26df081a
|
at26df081a
|
||||||
mr25h256
|
mr25h256
|
||||||
|
mr25h10
|
||||||
|
mr25h40
|
||||||
mx25l4005a
|
mx25l4005a
|
||||||
mx25l1606e
|
mx25l1606e
|
||||||
mx25l6405d
|
mx25l6405d
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
* Serial NOR flash controller for MTK MT81xx (and similar)
|
* Serial NOR flash controller for MTK MT81xx (and similar)
|
||||||
|
|
||||||
Required properties:
|
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
|
- reg: physical base address and length of the controller's register
|
||||||
- clocks: the phandle of the clocks needed by the nor controller
|
- clocks: the phandle of the clocks needed by the nor controller
|
||||||
- clock-names: the names of the clocks
|
- clock-names: the names of the clocks
|
||||||
|
88
Documentation/mtd/intel-spi.txt
Normal file
88
Documentation/mtd/intel-spi.txt
Normal file
@ -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 : Wildcat Point-LP
|
||||||
* document number TBD : 9 Series
|
* document number TBD : 9 Series
|
||||||
* document number TBD : Lewisburg
|
* document number TBD : Lewisburg
|
||||||
|
* document number TBD : Apollo Lake SoC
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
@ -83,6 +84,17 @@
|
|||||||
#define ACPIBASE_GCS_OFF 0x3410
|
#define ACPIBASE_GCS_OFF 0x3410
|
||||||
#define ACPIBASE_GCS_END 0x3414
|
#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 GPIOBASE_ICH0 0x58
|
||||||
#define GPIOCTRL_ICH0 0x5C
|
#define GPIOCTRL_ICH0 0x5C
|
||||||
#define GPIOBASE_ICH6 0x48
|
#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 = {
|
static struct mfd_cell lpc_ich_wdt_cell = {
|
||||||
.name = "iTCO_wdt",
|
.name = "iTCO_wdt",
|
||||||
.num_resources = ARRAY_SIZE(wdt_ich_res),
|
.num_resources = ARRAY_SIZE(wdt_ich_res),
|
||||||
@ -147,6 +165,14 @@ static struct mfd_cell lpc_ich_gpio_cell = {
|
|||||||
.ignore_resource_conflicts = true,
|
.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 */
|
/* chipset related info */
|
||||||
enum lpc_chipsets {
|
enum lpc_chipsets {
|
||||||
LPC_ICH = 0, /* ICH */
|
LPC_ICH = 0, /* ICH */
|
||||||
@ -216,6 +242,7 @@ enum lpc_chipsets {
|
|||||||
LPC_BRASWELL, /* Braswell SoC */
|
LPC_BRASWELL, /* Braswell SoC */
|
||||||
LPC_LEWISBURG, /* Lewisburg */
|
LPC_LEWISBURG, /* Lewisburg */
|
||||||
LPC_9S, /* 9 Series */
|
LPC_9S, /* 9 Series */
|
||||||
|
LPC_APL, /* Apollo Lake SoC */
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct lpc_ich_info lpc_chipset_info[] = {
|
static struct lpc_ich_info lpc_chipset_info[] = {
|
||||||
@ -494,10 +521,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||||||
.name = "Lynx Point",
|
.name = "Lynx Point",
|
||||||
.iTCO_version = 2,
|
.iTCO_version = 2,
|
||||||
.gpio_version = ICH_V5_GPIO,
|
.gpio_version = ICH_V5_GPIO,
|
||||||
|
.spi_type = INTEL_SPI_LPT,
|
||||||
},
|
},
|
||||||
[LPC_LPT_LP] = {
|
[LPC_LPT_LP] = {
|
||||||
.name = "Lynx Point_LP",
|
.name = "Lynx Point_LP",
|
||||||
.iTCO_version = 2,
|
.iTCO_version = 2,
|
||||||
|
.spi_type = INTEL_SPI_LPT,
|
||||||
},
|
},
|
||||||
[LPC_WBG] = {
|
[LPC_WBG] = {
|
||||||
.name = "Wellsburg",
|
.name = "Wellsburg",
|
||||||
@ -511,6 +540,7 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||||||
[LPC_BAYTRAIL] = {
|
[LPC_BAYTRAIL] = {
|
||||||
.name = "Bay Trail SoC",
|
.name = "Bay Trail SoC",
|
||||||
.iTCO_version = 3,
|
.iTCO_version = 3,
|
||||||
|
.spi_type = INTEL_SPI_BYT,
|
||||||
},
|
},
|
||||||
[LPC_COLETO] = {
|
[LPC_COLETO] = {
|
||||||
.name = "Coleto Creek",
|
.name = "Coleto Creek",
|
||||||
@ -519,10 +549,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||||||
[LPC_WPT_LP] = {
|
[LPC_WPT_LP] = {
|
||||||
.name = "Wildcat Point_LP",
|
.name = "Wildcat Point_LP",
|
||||||
.iTCO_version = 2,
|
.iTCO_version = 2,
|
||||||
|
.spi_type = INTEL_SPI_LPT,
|
||||||
},
|
},
|
||||||
[LPC_BRASWELL] = {
|
[LPC_BRASWELL] = {
|
||||||
.name = "Braswell SoC",
|
.name = "Braswell SoC",
|
||||||
.iTCO_version = 3,
|
.iTCO_version = 3,
|
||||||
|
.spi_type = INTEL_SPI_BYT,
|
||||||
},
|
},
|
||||||
[LPC_LEWISBURG] = {
|
[LPC_LEWISBURG] = {
|
||||||
.name = "Lewisburg",
|
.name = "Lewisburg",
|
||||||
@ -533,6 +565,10 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
|||||||
.iTCO_version = 2,
|
.iTCO_version = 2,
|
||||||
.gpio_version = ICH_V5_GPIO,
|
.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, 0x3b14), LPC_3420},
|
||||||
{ PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
|
{ PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
|
||||||
{ PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
|
{ PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
|
||||||
|
{ PCI_VDEVICE(INTEL, 0x5ae8), LPC_APL},
|
||||||
{ PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
|
{ PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
|
||||||
{ PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
|
{ PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
|
||||||
{ PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
|
{ PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
|
||||||
@ -1056,6 +1093,94 @@ wdt_done:
|
|||||||
return ret;
|
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,
|
static int lpc_ich_probe(struct pci_dev *dev,
|
||||||
const struct pci_device_id *id)
|
const struct pci_device_id *id)
|
||||||
{
|
{
|
||||||
@ -1099,6 +1224,12 @@ static int lpc_ich_probe(struct pci_dev *dev,
|
|||||||
cell_added = true;
|
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
|
* We only care if at least one or none of the cells registered
|
||||||
* successfully.
|
* successfully.
|
||||||
|
@ -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_buf = buf;
|
||||||
t[1].rx_nbits = m25p80_rx_nbits(nor);
|
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);
|
spi_message_add_tail(&t[1], &m);
|
||||||
|
|
||||||
ret = spi_sync(spi, &m);
|
ret = spi_sync(spi, &m);
|
||||||
@ -288,7 +289,6 @@ static const struct spi_device_id m25p_ids[] = {
|
|||||||
* should be kept for backward compatibility.
|
* should be kept for backward compatibility.
|
||||||
*/
|
*/
|
||||||
{"at25df321a"}, {"at25df641"}, {"at26df081a"},
|
{"at25df321a"}, {"at25df641"}, {"at26df081a"},
|
||||||
{"mr25h256"},
|
|
||||||
{"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
{"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
||||||
{"mx25l25635e"},{"mx66l51235l"},
|
{"mx25l25635e"},{"mx66l51235l"},
|
||||||
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
|
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
|
||||||
@ -305,6 +305,11 @@ static const struct spi_device_id m25p_ids[] = {
|
|||||||
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
||||||
{"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-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);
|
MODULE_DEVICE_TABLE(spi, m25p_ids);
|
||||||
|
@ -18,19 +18,12 @@
|
|||||||
#define SPINOR_OP_RDVCR 0x85
|
#define SPINOR_OP_RDVCR 0x85
|
||||||
|
|
||||||
/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */
|
/* 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 0x02 /* PAGE PROGRAM */
|
||||||
#define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT 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_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */
|
||||||
#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */
|
#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */
|
||||||
#define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT 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 */
|
/* Configuration flags */
|
||||||
#define FLASH_FLAG_SINGLE 0x000000ff
|
#define FLASH_FLAG_SINGLE 0x000000ff
|
||||||
#define FLASH_FLAG_READ_WRITE 0x00000001
|
#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.)
|
* - 'FAST' variants configured for 8 dummy cycles (see note above.)
|
||||||
*/
|
*/
|
||||||
static struct seq_rw_config n25q_read4_configs[] = {
|
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_4_4, SPINOR_OP_READ_1_4_4_4B, 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_1_4, SPINOR_OP_READ_1_1_4_4B, 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_2_2, SPINOR_OP_READ_1_2_2_4B, 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_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||||
{0x00, 0, 0, 0, 0, 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.
|
* entering a state that is incompatible with the SPIBoot Controller.
|
||||||
*/
|
*/
|
||||||
static struct seq_rw_config stfsm_s25fl_read4_configs[] = {
|
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_4_4, SPINOR_OP_READ_1_4_4_4B, 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_1_4, SPINOR_OP_READ_1_1_4_4B, 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_2_2, SPINOR_OP_READ_1_2_2_4B, 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_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct seq_rw_config stfsm_s25fl_write4_configs[] = {
|
static struct seq_rw_config stfsm_s25fl_write4_configs[] = {
|
||||||
|
@ -29,6 +29,16 @@ config MTD_SPI_NOR_USE_4K_SECTORS
|
|||||||
Please note that some tools/drivers/filesystems may not work with
|
Please note that some tools/drivers/filesystems may not work with
|
||||||
4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
|
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
|
config SPI_ATMEL_QUADSPI
|
||||||
tristate "Atmel Quad SPI Controller"
|
tristate "Atmel Quad SPI Controller"
|
||||||
depends on ARCH_AT91 || (ARM && COMPILE_TEST)
|
depends on ARCH_AT91 || (ARM && COMPILE_TEST)
|
||||||
@ -40,7 +50,7 @@ config SPI_ATMEL_QUADSPI
|
|||||||
|
|
||||||
config SPI_CADENCE_QUADSPI
|
config SPI_CADENCE_QUADSPI
|
||||||
tristate "Cadence Quad SPI controller"
|
tristate "Cadence Quad SPI controller"
|
||||||
depends on OF && ARM
|
depends on OF && (ARM || COMPILE_TEST)
|
||||||
help
|
help
|
||||||
Enable support for the Cadence Quad SPI Flash controller.
|
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
|
Flash. Enable this option if you have a device with a SPIFI
|
||||||
controller and want to access the Flash as a mtd device.
|
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
|
endif # MTD_SPI_NOR
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
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_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||||
obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
|
obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
|
||||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.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
|
||||||
|
758
drivers/mtd/spi-nor/aspeed-smc.c
Normal file
758
drivers/mtd/spi-nor/aspeed-smc.c
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
/*
|
||||||
|
* 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)) {
|
||||||
|
dev_err(dev, "Cannot remap controller address.\n");
|
||||||
|
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)) {
|
||||||
|
dev_err(dev, "Cannot remap controller address.\n");
|
||||||
|
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 *= cqspi->fifo_width;
|
||||||
bytes_to_read = bytes_to_read > remaining ?
|
bytes_to_read = bytes_to_read > remaining ?
|
||||||
remaining : bytes_to_read;
|
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;
|
rxbuf += bytes_to_read;
|
||||||
remaining -= bytes_to_read;
|
remaining -= bytes_to_read;
|
||||||
bytes_to_read = cqspi_get_rd_sram_level(cqspi);
|
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) {
|
while (remaining > 0) {
|
||||||
write_bytes = remaining > page_size ? page_size : remaining;
|
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,
|
ret = wait_for_completion_timeout(&cqspi->transfer_complete,
|
||||||
msecs_to_jiffies
|
msecs_to_jiffies
|
||||||
@ -891,7 +893,7 @@ static ssize_t cqspi_write(struct spi_nor *nor, loff_t to,
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
return (ret < 0) ? ret : len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
|
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)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
return (ret < 0) ? ret : len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cqspi_erase(struct spi_nor *nor, loff_t offs)
|
static int cqspi_erase(struct spi_nor *nor, loff_t offs)
|
||||||
|
@ -193,7 +193,7 @@
|
|||||||
#define QUADSPI_LUT_NUM 64
|
#define QUADSPI_LUT_NUM 64
|
||||||
|
|
||||||
/* SEQID -- we can have 16 seqids at most. */
|
/* SEQID -- we can have 16 seqids at most. */
|
||||||
#define SEQID_QUAD_READ 0
|
#define SEQID_READ 0
|
||||||
#define SEQID_WREN 1
|
#define SEQID_WREN 1
|
||||||
#define SEQID_WRDI 2
|
#define SEQID_WRDI 2
|
||||||
#define SEQID_RDSR 3
|
#define SEQID_RDSR 3
|
||||||
@ -373,32 +373,26 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||||||
void __iomem *base = q->iobase;
|
void __iomem *base = q->iobase;
|
||||||
int rxfifo = q->devtype_data->rxfifo;
|
int rxfifo = q->devtype_data->rxfifo;
|
||||||
u32 lut_base;
|
u32 lut_base;
|
||||||
u8 cmd, addrlen, dummy;
|
|
||||||
int i;
|
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);
|
fsl_qspi_unlock_lut(q);
|
||||||
|
|
||||||
/* Clear all the LUT table */
|
/* Clear all the LUT table */
|
||||||
for (i = 0; i < QUADSPI_LUT_NUM; i++)
|
for (i = 0; i < QUADSPI_LUT_NUM; i++)
|
||||||
qspi_writel(q, 0, base + QUADSPI_LUT_BASE + i * 4);
|
qspi_writel(q, 0, base + QUADSPI_LUT_BASE + i * 4);
|
||||||
|
|
||||||
/* Quad Read */
|
/* Read */
|
||||||
lut_base = SEQID_QUAD_READ * 4;
|
lut_base = SEQID_READ * 4;
|
||||||
|
|
||||||
if (q->nor_size <= SZ_16M) {
|
qspi_writel(q, LUT0(CMD, PAD1, read_op) | LUT1(ADDR, PAD1, addrlen),
|
||||||
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),
|
|
||||||
base + QUADSPI_LUT(lut_base));
|
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));
|
base + QUADSPI_LUT(lut_base + 1));
|
||||||
|
|
||||||
/* Write enable */
|
/* Write enable */
|
||||||
@ -409,16 +403,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||||||
/* Page Program */
|
/* Page Program */
|
||||||
lut_base = SEQID_PP * 4;
|
lut_base = SEQID_PP * 4;
|
||||||
|
|
||||||
if (q->nor_size <= SZ_16M) {
|
qspi_writel(q, LUT0(CMD, PAD1, nor->program_opcode) |
|
||||||
cmd = SPINOR_OP_PP;
|
LUT1(ADDR, PAD1, addrlen),
|
||||||
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),
|
|
||||||
base + QUADSPI_LUT(lut_base));
|
base + QUADSPI_LUT(lut_base));
|
||||||
qspi_writel(q, LUT0(FSL_WRITE, PAD1, 0),
|
qspi_writel(q, LUT0(FSL_WRITE, PAD1, 0),
|
||||||
base + QUADSPI_LUT(lut_base + 1));
|
base + QUADSPI_LUT(lut_base + 1));
|
||||||
@ -432,10 +418,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
|||||||
/* Erase a sector */
|
/* Erase a sector */
|
||||||
lut_base = SEQID_SE * 4;
|
lut_base = SEQID_SE * 4;
|
||||||
|
|
||||||
cmd = q->nor[0].erase_opcode;
|
qspi_writel(q, LUT0(CMD, PAD1, nor->erase_opcode) |
|
||||||
addrlen = q->nor_size <= SZ_16M ? ADDR24BIT : ADDR32BIT;
|
LUT1(ADDR, PAD1, addrlen),
|
||||||
|
|
||||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
|
||||||
base + QUADSPI_LUT(lut_base));
|
base + QUADSPI_LUT(lut_base));
|
||||||
|
|
||||||
/* Erase the whole chip */
|
/* Erase the whole chip */
|
||||||
@ -484,7 +468,7 @@ static int fsl_qspi_get_seqid(struct fsl_qspi *q, u8 cmd)
|
|||||||
{
|
{
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case SPINOR_OP_READ_1_1_4:
|
case SPINOR_OP_READ_1_1_4:
|
||||||
return SEQID_QUAD_READ;
|
return SEQID_READ;
|
||||||
case SPINOR_OP_WREN:
|
case SPINOR_OP_WREN:
|
||||||
return SEQID_WREN;
|
return SEQID_WREN;
|
||||||
case SPINOR_OP_WRDI:
|
case SPINOR_OP_WRDI:
|
||||||
|
57
drivers/mtd/spi-nor/intel-spi-platform.c
Normal file
57
drivers/mtd/spi-nor/intel-spi-platform.c
Normal file
@ -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");
|
777
drivers/mtd/spi-nor/intel-spi.c
Normal file
777
drivers/mtd/spi-nor/intel-spi.c
Normal file
@ -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");
|
24
drivers/mtd/spi-nor/intel-spi.h
Normal file
24
drivers/mtd/spi-nor/intel-spi.h
Normal file
@ -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
|
* bit. Must be used with
|
||||||
* SPI_NOR_HAS_LOCK.
|
* 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])
|
#define JEDEC_MFR(info) ((info)->id[0])
|
||||||
@ -188,6 +198,78 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
|||||||
return mtd->priv;
|
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. */
|
/* Enable/disable 4-byte addressing mode. */
|
||||||
static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
||||||
int enable)
|
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);
|
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)
|
static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||||
{
|
{
|
||||||
int sr = read_sr(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)
|
static int spi_nor_ready(struct spi_nor *nor)
|
||||||
{
|
{
|
||||||
int sr, fsr;
|
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)
|
if (sr < 0)
|
||||||
return sr;
|
return sr;
|
||||||
fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1;
|
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);
|
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
|
* 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];
|
u8 buf[SPI_NOR_MAX_ADDR_WIDTH];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||||
|
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||||
|
|
||||||
if (nor->erase)
|
if (nor->erase)
|
||||||
return nor->erase(nor, addr);
|
return nor->erase(nor, addr);
|
||||||
|
|
||||||
@ -368,7 +493,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
/* whole-chip erase? */
|
/* whole-chip erase? */
|
||||||
if (len == mtd->size) {
|
if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||||
unsigned long timeout;
|
unsigned long timeout;
|
||||||
|
|
||||||
write_enable(nor);
|
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), \
|
.addr_width = (_addr_width), \
|
||||||
.flags = (_flags),
|
.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
|
/* NOTE: double check command sets and memory organization when you add
|
||||||
* more nor chips. This current list focusses on newer chips, which
|
* more nor chips. This current list focusses on newer chips, which
|
||||||
* have been converging on command sets which including JEDEC ID.
|
* 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) },
|
{ "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128, SECT_4K) },
|
||||||
|
|
||||||
/* ESMT */
|
/* ESMT */
|
||||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K) },
|
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||||
|
|
||||||
/* Everspin */
|
/* Everspin */
|
||||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
{ "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) },
|
{ "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||||
|
|
||||||
/* GigaDevice */
|
/* 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,
|
"gd25q32", INFO(0xc84016, 0, 64 * 1024, 64,
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
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) },
|
{ "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) },
|
{ "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) },
|
{ "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;
|
return ret;
|
||||||
|
|
||||||
while (len) {
|
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) {
|
if (ret == 0) {
|
||||||
/* We shouldn't see 0-length reads */
|
/* We shouldn't see 0-length reads */
|
||||||
ret = -EIO;
|
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; ) {
|
for (i = 0; i < len; ) {
|
||||||
ssize_t written;
|
ssize_t written;
|
||||||
|
loff_t addr = to + i;
|
||||||
|
|
||||||
page_offset = (to + i) & (nor->page_size - 1);
|
/*
|
||||||
WARN_ONCE(page_offset,
|
* If page_size is a power of two, the offset can be quickly
|
||||||
"Writing at offset %zu into a NOR page. Writing partial pages may decrease reliability and increase wear of NOR flash.",
|
* calculated with an AND operation. On the other cases we
|
||||||
page_offset);
|
* 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 */
|
/* the size of data remaining on the first page */
|
||||||
page_remain = min_t(size_t,
|
page_remain = min_t(size_t,
|
||||||
nor->page_size - page_offset, len - i);
|
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);
|
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)
|
if (ret < 0)
|
||||||
goto write_err;
|
goto write_err;
|
||||||
written = ret;
|
written = ret;
|
||||||
@ -1216,6 +1386,9 @@ static int macronix_quad_enable(struct spi_nor *nor)
|
|||||||
val = read_sr(nor);
|
val = read_sr(nor);
|
||||||
if (val < 0)
|
if (val < 0)
|
||||||
return val;
|
return val;
|
||||||
|
if (val & SR_QUAD_EN_MX)
|
||||||
|
return 0;
|
||||||
|
|
||||||
write_enable(nor);
|
write_enable(nor);
|
||||||
|
|
||||||
write_sr(nor, val | SR_QUAD_EN_MX);
|
write_sr(nor, val | SR_QUAD_EN_MX);
|
||||||
@ -1312,6 +1485,47 @@ static int spi_nor_check(struct spi_nor *nor)
|
|||||||
return 0;
|
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)
|
int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||||
{
|
{
|
||||||
const struct flash_info *info = NULL;
|
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);
|
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
|
* Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
|
||||||
* with the software protection bits set
|
* 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) {
|
else if (mtd->size > 0x1000000) {
|
||||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||||
nor->addr_width = 4;
|
nor->addr_width = 4;
|
||||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION) {
|
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
|
||||||
/* Dedicated 4-byte command set */
|
info->flags & SPI_NOR_4B_OPCODES)
|
||||||
switch (nor->flash_read) {
|
spi_nor_set_4byte_opcodes(nor, info);
|
||||||
case SPI_NOR_QUAD:
|
else
|
||||||
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
|
|
||||||
set_4byte(nor, info, 1);
|
set_4byte(nor, info, 1);
|
||||||
} else {
|
} else {
|
||||||
nor->addr_width = 3;
|
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);
|
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,
|
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
||||||
(long long)mtd->size >> 10);
|
(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 */
|
/* default mode, does not need flex_cmd */
|
||||||
flex_mode = 0;
|
flex_mode = 0;
|
||||||
else
|
else
|
||||||
command = SPINOR_OP_READ4_FAST;
|
command = SPINOR_OP_READ_FAST_4B;
|
||||||
break;
|
break;
|
||||||
case SPI_NBITS_DUAL:
|
case SPI_NBITS_DUAL:
|
||||||
bpc = 0x00000001;
|
bpc = 0x00000001;
|
||||||
@ -384,7 +384,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
|||||||
} else {
|
} else {
|
||||||
command = SPINOR_OP_READ_1_1_2;
|
command = SPINOR_OP_READ_1_1_2;
|
||||||
if (spans_4byte)
|
if (spans_4byte)
|
||||||
command = SPINOR_OP_READ4_1_1_2;
|
command = SPINOR_OP_READ_1_1_2_4B;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SPI_NBITS_QUAD:
|
case SPI_NBITS_QUAD:
|
||||||
@ -399,7 +399,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
|||||||
} else {
|
} else {
|
||||||
command = SPINOR_OP_READ_1_1_4;
|
command = SPINOR_OP_READ_1_1_4;
|
||||||
if (spans_4byte)
|
if (spans_4byte)
|
||||||
command = SPINOR_OP_READ4_1_1_4;
|
command = SPINOR_OP_READ_1_1_4_4B;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#ifndef LPC_ICH_H
|
#ifndef LPC_ICH_H
|
||||||
#define LPC_ICH_H
|
#define LPC_ICH_H
|
||||||
|
|
||||||
|
#include <linux/platform_data/intel-spi.h>
|
||||||
|
|
||||||
/* GPIO resources */
|
/* GPIO resources */
|
||||||
#define ICH_RES_GPIO 0
|
#define ICH_RES_GPIO 0
|
||||||
#define ICH_RES_GPE0 1
|
#define ICH_RES_GPE0 1
|
||||||
@ -40,6 +42,7 @@ struct lpc_ich_info {
|
|||||||
char name[32];
|
char name[32];
|
||||||
unsigned int iTCO_version;
|
unsigned int iTCO_version;
|
||||||
unsigned int gpio_version;
|
unsigned int gpio_version;
|
||||||
|
enum intel_spi_type spi_type;
|
||||||
u8 use_gpio;
|
u8 use_gpio;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,9 +43,13 @@
|
|||||||
#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */
|
#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */
|
||||||
#define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */
|
#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_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_2 0x3b /* Read data bytes (Dual Output SPI) */
|
||||||
#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad 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 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 0x20 /* Erase 4KiB block */
|
||||||
#define SPINOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */
|
#define SPINOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */
|
||||||
#define SPINOR_OP_BE_32K 0x52 /* Erase 32KiB block */
|
#define SPINOR_OP_BE_32K 0x52 /* Erase 32KiB block */
|
||||||
@ -56,11 +60,17 @@
|
|||||||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||||
|
|
||||||
/* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
|
/* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
|
||||||
#define SPINOR_OP_READ4 0x13 /* Read data bytes (low frequency) */
|
#define SPINOR_OP_READ_4B 0x13 /* Read data bytes (low frequency) */
|
||||||
#define SPINOR_OP_READ4_FAST 0x0c /* Read data bytes (high frequency) */
|
#define SPINOR_OP_READ_FAST_4B 0x0c /* Read data bytes (high frequency) */
|
||||||
#define SPINOR_OP_READ4_1_1_2 0x3c /* Read data bytes (Dual SPI) */
|
#define SPINOR_OP_READ_1_1_2_4B 0x3c /* Read data bytes (Dual Output SPI) */
|
||||||
#define SPINOR_OP_READ4_1_1_4 0x6c /* Read data bytes (Quad 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_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) */
|
#define SPINOR_OP_SE_4B 0xdc /* Sector erase (usually 64KiB) */
|
||||||
|
|
||||||
/* Used for SST flashes only. */
|
/* Used for SST flashes only. */
|
||||||
@ -68,6 +78,15 @@
|
|||||||
#define SPINOR_OP_WRDI 0x04 /* Write disable */
|
#define SPINOR_OP_WRDI 0x04 /* Write disable */
|
||||||
#define SPINOR_OP_AAI_WP 0xad /* Auto address increment word program */
|
#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. */
|
/* Used for Macronix and Winbond flashes. */
|
||||||
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
|
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
|
||||||
#define SPINOR_OP_EX4B 0xe9 /* Exit 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 {
|
enum spi_nor_option_flags {
|
||||||
SNOR_F_USE_FSR = BIT(0),
|
SNOR_F_USE_FSR = BIT(0),
|
||||||
SNOR_F_HAS_SR_TB = BIT(1),
|
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),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
31
include/linux/platform_data/intel-spi.h
Normal file
31
include/linux/platform_data/intel-spi.h
Normal file
@ -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…
x
Reference in New Issue
Block a user