mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-24 13:13:57 +08:00
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
|
||||
at26df081a
|
||||
mr25h256
|
||||
mr25h10
|
||||
mr25h40
|
||||
mx25l4005a
|
||||
mx25l1606e
|
||||
mx25l6405d
|
||||
|
@ -1,7 +1,13 @@
|
||||
* Serial NOR flash controller for MTK MT81xx (and similar)
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "mediatek,mt8173-nor";
|
||||
- compatible: The possible values are:
|
||||
"mediatek,mt2701-nor"
|
||||
"mediatek,mt7623-nor"
|
||||
"mediatek,mt8173-nor"
|
||||
For mt8173, compatible should be "mediatek,mt8173-nor".
|
||||
For every other SoC, should contain both the SoC-specific compatible string
|
||||
and "mediatek,mt8173-nor".
|
||||
- reg: physical base address and length of the controller's register
|
||||
- clocks: the phandle of the clocks needed by the nor controller
|
||||
- clock-names: the names of the clocks
|
||||
|
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 : 9 Series
|
||||
* document number TBD : Lewisburg
|
||||
* document number TBD : Apollo Lake SoC
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
@ -83,6 +84,17 @@
|
||||
#define ACPIBASE_GCS_OFF 0x3410
|
||||
#define ACPIBASE_GCS_END 0x3414
|
||||
|
||||
#define SPIBASE_BYT 0x54
|
||||
#define SPIBASE_BYT_SZ 512
|
||||
#define SPIBASE_BYT_EN BIT(1)
|
||||
|
||||
#define SPIBASE_LPT 0x3800
|
||||
#define SPIBASE_LPT_SZ 512
|
||||
#define BCR 0xdc
|
||||
#define BCR_WPD BIT(0)
|
||||
|
||||
#define SPIBASE_APL_SZ 4096
|
||||
|
||||
#define GPIOBASE_ICH0 0x58
|
||||
#define GPIOCTRL_ICH0 0x5C
|
||||
#define GPIOBASE_ICH6 0x48
|
||||
@ -133,6 +145,12 @@ static struct resource gpio_ich_res[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource intel_spi_res[] = {
|
||||
{
|
||||
.flags = IORESOURCE_MEM,
|
||||
}
|
||||
};
|
||||
|
||||
static struct mfd_cell lpc_ich_wdt_cell = {
|
||||
.name = "iTCO_wdt",
|
||||
.num_resources = ARRAY_SIZE(wdt_ich_res),
|
||||
@ -147,6 +165,14 @@ static struct mfd_cell lpc_ich_gpio_cell = {
|
||||
.ignore_resource_conflicts = true,
|
||||
};
|
||||
|
||||
|
||||
static struct mfd_cell lpc_ich_spi_cell = {
|
||||
.name = "intel-spi",
|
||||
.num_resources = ARRAY_SIZE(intel_spi_res),
|
||||
.resources = intel_spi_res,
|
||||
.ignore_resource_conflicts = true,
|
||||
};
|
||||
|
||||
/* chipset related info */
|
||||
enum lpc_chipsets {
|
||||
LPC_ICH = 0, /* ICH */
|
||||
@ -216,6 +242,7 @@ enum lpc_chipsets {
|
||||
LPC_BRASWELL, /* Braswell SoC */
|
||||
LPC_LEWISBURG, /* Lewisburg */
|
||||
LPC_9S, /* 9 Series */
|
||||
LPC_APL, /* Apollo Lake SoC */
|
||||
};
|
||||
|
||||
static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
@ -494,10 +521,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
.name = "Lynx Point",
|
||||
.iTCO_version = 2,
|
||||
.gpio_version = ICH_V5_GPIO,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_LPT_LP] = {
|
||||
.name = "Lynx Point_LP",
|
||||
.iTCO_version = 2,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_WBG] = {
|
||||
.name = "Wellsburg",
|
||||
@ -511,6 +540,7 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
[LPC_BAYTRAIL] = {
|
||||
.name = "Bay Trail SoC",
|
||||
.iTCO_version = 3,
|
||||
.spi_type = INTEL_SPI_BYT,
|
||||
},
|
||||
[LPC_COLETO] = {
|
||||
.name = "Coleto Creek",
|
||||
@ -519,10 +549,12 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
[LPC_WPT_LP] = {
|
||||
.name = "Wildcat Point_LP",
|
||||
.iTCO_version = 2,
|
||||
.spi_type = INTEL_SPI_LPT,
|
||||
},
|
||||
[LPC_BRASWELL] = {
|
||||
.name = "Braswell SoC",
|
||||
.iTCO_version = 3,
|
||||
.spi_type = INTEL_SPI_BYT,
|
||||
},
|
||||
[LPC_LEWISBURG] = {
|
||||
.name = "Lewisburg",
|
||||
@ -533,6 +565,10 @@ static struct lpc_ich_info lpc_chipset_info[] = {
|
||||
.iTCO_version = 2,
|
||||
.gpio_version = ICH_V5_GPIO,
|
||||
},
|
||||
[LPC_APL] = {
|
||||
.name = "Apollo Lake SoC",
|
||||
.spi_type = INTEL_SPI_BXT,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
@ -681,6 +717,7 @@ static const struct pci_device_id lpc_ich_ids[] = {
|
||||
{ PCI_VDEVICE(INTEL, 0x3b14), LPC_3420},
|
||||
{ PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
|
||||
{ PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
|
||||
{ PCI_VDEVICE(INTEL, 0x5ae8), LPC_APL},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
|
||||
{ PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
|
||||
@ -1056,6 +1093,94 @@ wdt_done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpc_ich_init_spi(struct pci_dev *dev)
|
||||
{
|
||||
struct lpc_ich_priv *priv = pci_get_drvdata(dev);
|
||||
struct resource *res = &intel_spi_res[0];
|
||||
struct intel_spi_boardinfo *info;
|
||||
u32 spi_base, rcba, bcr;
|
||||
|
||||
info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->type = lpc_chipset_info[priv->chipset].spi_type;
|
||||
|
||||
switch (info->type) {
|
||||
case INTEL_SPI_BYT:
|
||||
pci_read_config_dword(dev, SPIBASE_BYT, &spi_base);
|
||||
if (spi_base & SPIBASE_BYT_EN) {
|
||||
res->start = spi_base & ~(SPIBASE_BYT_SZ - 1);
|
||||
res->end = res->start + SPIBASE_BYT_SZ - 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case INTEL_SPI_LPT:
|
||||
pci_read_config_dword(dev, RCBABASE, &rcba);
|
||||
if (rcba & 1) {
|
||||
spi_base = round_down(rcba, SPIBASE_LPT_SZ);
|
||||
res->start = spi_base + SPIBASE_LPT;
|
||||
res->end = res->start + SPIBASE_LPT_SZ - 1;
|
||||
|
||||
/*
|
||||
* Try to make the flash chip writeable now by
|
||||
* setting BCR_WPD. It it fails we tell the driver
|
||||
* that it can only read the chip.
|
||||
*/
|
||||
pci_read_config_dword(dev, BCR, &bcr);
|
||||
if (!(bcr & BCR_WPD)) {
|
||||
bcr |= BCR_WPD;
|
||||
pci_write_config_dword(dev, BCR, bcr);
|
||||
pci_read_config_dword(dev, BCR, &bcr);
|
||||
}
|
||||
info->writeable = !!(bcr & BCR_WPD);
|
||||
}
|
||||
break;
|
||||
|
||||
case INTEL_SPI_BXT: {
|
||||
unsigned int p2sb = PCI_DEVFN(13, 0);
|
||||
unsigned int spi = PCI_DEVFN(13, 2);
|
||||
struct pci_bus *bus = dev->bus;
|
||||
|
||||
/*
|
||||
* The P2SB is hidden by BIOS and we need to unhide it in
|
||||
* order to read BAR of the SPI flash device. Once that is
|
||||
* done we hide it again.
|
||||
*/
|
||||
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x0);
|
||||
pci_bus_read_config_dword(bus, spi, PCI_BASE_ADDRESS_0,
|
||||
&spi_base);
|
||||
if (spi_base != ~0) {
|
||||
res->start = spi_base & 0xfffffff0;
|
||||
res->end = res->start + SPIBASE_APL_SZ - 1;
|
||||
|
||||
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
|
||||
if (!(bcr & BCR_WPD)) {
|
||||
bcr |= BCR_WPD;
|
||||
pci_bus_write_config_dword(bus, spi, BCR, bcr);
|
||||
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
|
||||
}
|
||||
info->writeable = !!(bcr & BCR_WPD);
|
||||
}
|
||||
|
||||
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!res->start)
|
||||
return -ENODEV;
|
||||
|
||||
lpc_ich_spi_cell.platform_data = info;
|
||||
lpc_ich_spi_cell.pdata_size = sizeof(*info);
|
||||
|
||||
return mfd_add_devices(&dev->dev, PLATFORM_DEVID_NONE,
|
||||
&lpc_ich_spi_cell, 1, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int lpc_ich_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
@ -1099,6 +1224,12 @@ static int lpc_ich_probe(struct pci_dev *dev,
|
||||
cell_added = true;
|
||||
}
|
||||
|
||||
if (lpc_chipset_info[priv->chipset].spi_type) {
|
||||
ret = lpc_ich_init_spi(dev);
|
||||
if (!ret)
|
||||
cell_added = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only care if at least one or none of the cells registered
|
||||
* successfully.
|
||||
|
@ -172,7 +172,8 @@ static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].rx_nbits = m25p80_rx_nbits(nor);
|
||||
t[1].len = min(len, spi_max_transfer_size(spi));
|
||||
t[1].len = min3(len, spi_max_transfer_size(spi),
|
||||
spi_max_message_size(spi) - t[0].len);
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
ret = spi_sync(spi, &m);
|
||||
@ -288,7 +289,6 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
* should be kept for backward compatibility.
|
||||
*/
|
||||
{"at25df321a"}, {"at25df641"}, {"at26df081a"},
|
||||
{"mr25h256"},
|
||||
{"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
||||
{"mx25l25635e"},{"mx66l51235l"},
|
||||
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
|
||||
@ -305,6 +305,11 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
||||
{"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"},
|
||||
|
||||
/* Everspin MRAMs (non-JEDEC) */
|
||||
{ "mr25h256" }, /* 256 Kib, 40 MHz */
|
||||
{ "mr25h10" }, /* 1 Mib, 40 MHz */
|
||||
{ "mr25h40" }, /* 4 Mib, 40 MHz */
|
||||
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, m25p_ids);
|
||||
|
@ -18,19 +18,12 @@
|
||||
#define SPINOR_OP_RDVCR 0x85
|
||||
|
||||
/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */
|
||||
#define SPINOR_OP_READ_1_2_2 0xbb /* DUAL I/O READ */
|
||||
#define SPINOR_OP_READ_1_4_4 0xeb /* QUAD I/O READ */
|
||||
|
||||
#define SPINOR_OP_WRITE 0x02 /* PAGE PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT PROGRAM */
|
||||
|
||||
/* READ commands with 32-bit addressing */
|
||||
#define SPINOR_OP_READ4_1_2_2 0xbc
|
||||
#define SPINOR_OP_READ4_1_4_4 0xec
|
||||
|
||||
/* Configuration flags */
|
||||
#define FLASH_FLAG_SINGLE 0x000000ff
|
||||
#define FLASH_FLAG_READ_WRITE 0x00000001
|
||||
|
@ -507,13 +507,13 @@ static struct seq_rw_config n25q_read3_configs[] = {
|
||||
* - 'FAST' variants configured for 8 dummy cycles (see note above.)
|
||||
*/
|
||||
static struct seq_rw_config n25q_read4_configs[] = {
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
};
|
||||
|
||||
/*
|
||||
@ -553,13 +553,13 @@ static int stfsm_mx25_en_32bit_addr_seq(struct stfsm_seq *seq)
|
||||
* entering a state that is incompatible with the SPIBoot Controller.
|
||||
*/
|
||||
static struct seq_rw_config stfsm_s25fl_read4_configs[] = {
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 2, 4},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 4, 0},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
{FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 2, 4},
|
||||
{FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 4, 0},
|
||||
{FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8},
|
||||
{FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0},
|
||||
{0x00, 0, 0, 0, 0, 0x00, 0, 0},
|
||||
};
|
||||
|
||||
static struct seq_rw_config stfsm_s25fl_write4_configs[] = {
|
||||
|
@ -29,6 +29,16 @@ config MTD_SPI_NOR_USE_4K_SECTORS
|
||||
Please note that some tools/drivers/filesystems may not work with
|
||||
4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
|
||||
|
||||
config SPI_ASPEED_SMC
|
||||
tristate "Aspeed flash controllers in SPI mode"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on HAS_IOMEM && OF
|
||||
help
|
||||
This enables support for the Firmware Memory controller (FMC)
|
||||
in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips,
|
||||
and support for the SPI flash memory controller (SPI) for
|
||||
the host firmware. The implementation only supports SPI NOR.
|
||||
|
||||
config SPI_ATMEL_QUADSPI
|
||||
tristate "Atmel Quad SPI Controller"
|
||||
depends on ARCH_AT91 || (ARM && COMPILE_TEST)
|
||||
@ -40,7 +50,7 @@ config SPI_ATMEL_QUADSPI
|
||||
|
||||
config SPI_CADENCE_QUADSPI
|
||||
tristate "Cadence Quad SPI controller"
|
||||
depends on OF && ARM
|
||||
depends on OF && (ARM || COMPILE_TEST)
|
||||
help
|
||||
Enable support for the Cadence Quad SPI Flash controller.
|
||||
|
||||
@ -76,4 +86,24 @@ config SPI_NXP_SPIFI
|
||||
Flash. Enable this option if you have a device with a SPIFI
|
||||
controller and want to access the Flash as a mtd device.
|
||||
|
||||
config SPI_INTEL_SPI
|
||||
tristate
|
||||
|
||||
config SPI_INTEL_SPI_PLATFORM
|
||||
tristate "Intel PCH/PCU SPI flash platform driver" if EXPERT
|
||||
depends on X86
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables platform support for the Intel PCH/PCU SPI
|
||||
controller in master mode. This controller is present in modern
|
||||
Intel hardware and is used to hold BIOS and other persistent
|
||||
settings. Using this driver it is possible to upgrade BIOS
|
||||
directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-platform.
|
||||
|
||||
endif # MTD_SPI_NOR
|
||||
|
@ -1,7 +1,10 @@
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
|
||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
|
||||
|
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 = bytes_to_read > remaining ?
|
||||
remaining : bytes_to_read;
|
||||
readsl(ahb_base, rxbuf, DIV_ROUND_UP(bytes_to_read, 4));
|
||||
ioread32_rep(ahb_base, rxbuf,
|
||||
DIV_ROUND_UP(bytes_to_read, 4));
|
||||
rxbuf += bytes_to_read;
|
||||
remaining -= bytes_to_read;
|
||||
bytes_to_read = cqspi_get_rd_sram_level(cqspi);
|
||||
@ -610,7 +611,8 @@ static int cqspi_indirect_write_execute(struct spi_nor *nor,
|
||||
|
||||
while (remaining > 0) {
|
||||
write_bytes = remaining > page_size ? page_size : remaining;
|
||||
writesl(cqspi->ahb_base, txbuf, DIV_ROUND_UP(write_bytes, 4));
|
||||
iowrite32_rep(cqspi->ahb_base, txbuf,
|
||||
DIV_ROUND_UP(write_bytes, 4));
|
||||
|
||||
ret = wait_for_completion_timeout(&cqspi->transfer_complete,
|
||||
msecs_to_jiffies
|
||||
@ -891,7 +893,7 @@ static ssize_t cqspi_write(struct spi_nor *nor, loff_t to,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return (ret < 0) ? ret : len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
|
||||
@ -911,7 +913,7 @@ static ssize_t cqspi_read(struct spi_nor *nor, loff_t from,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return (ret < 0) ? ret : len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int cqspi_erase(struct spi_nor *nor, loff_t offs)
|
||||
|
@ -193,7 +193,7 @@
|
||||
#define QUADSPI_LUT_NUM 64
|
||||
|
||||
/* SEQID -- we can have 16 seqids at most. */
|
||||
#define SEQID_QUAD_READ 0
|
||||
#define SEQID_READ 0
|
||||
#define SEQID_WREN 1
|
||||
#define SEQID_WRDI 2
|
||||
#define SEQID_RDSR 3
|
||||
@ -373,32 +373,26 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
||||
void __iomem *base = q->iobase;
|
||||
int rxfifo = q->devtype_data->rxfifo;
|
||||
u32 lut_base;
|
||||
u8 cmd, addrlen, dummy;
|
||||
int i;
|
||||
|
||||
struct spi_nor *nor = &q->nor[0];
|
||||
u8 addrlen = (nor->addr_width == 3) ? ADDR24BIT : ADDR32BIT;
|
||||
u8 read_op = nor->read_opcode;
|
||||
u8 read_dm = nor->read_dummy;
|
||||
|
||||
fsl_qspi_unlock_lut(q);
|
||||
|
||||
/* Clear all the LUT table */
|
||||
for (i = 0; i < QUADSPI_LUT_NUM; i++)
|
||||
qspi_writel(q, 0, base + QUADSPI_LUT_BASE + i * 4);
|
||||
|
||||
/* Quad Read */
|
||||
lut_base = SEQID_QUAD_READ * 4;
|
||||
/* Read */
|
||||
lut_base = SEQID_READ * 4;
|
||||
|
||||
if (q->nor_size <= SZ_16M) {
|
||||
cmd = SPINOR_OP_READ_1_1_4;
|
||||
addrlen = ADDR24BIT;
|
||||
dummy = 8;
|
||||
} else {
|
||||
/* use the 4-byte address */
|
||||
cmd = SPINOR_OP_READ_1_1_4;
|
||||
addrlen = ADDR32BIT;
|
||||
dummy = 8;
|
||||
}
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, read_op) | LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
qspi_writel(q, LUT0(DUMMY, PAD1, dummy) | LUT1(FSL_READ, PAD4, rxfifo),
|
||||
qspi_writel(q, LUT0(DUMMY, PAD1, read_dm) |
|
||||
LUT1(FSL_READ, PAD4, rxfifo),
|
||||
base + QUADSPI_LUT(lut_base + 1));
|
||||
|
||||
/* Write enable */
|
||||
@ -409,16 +403,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
||||
/* Page Program */
|
||||
lut_base = SEQID_PP * 4;
|
||||
|
||||
if (q->nor_size <= SZ_16M) {
|
||||
cmd = SPINOR_OP_PP;
|
||||
addrlen = ADDR24BIT;
|
||||
} else {
|
||||
/* use the 4-byte address */
|
||||
cmd = SPINOR_OP_PP;
|
||||
addrlen = ADDR32BIT;
|
||||
}
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, nor->program_opcode) |
|
||||
LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
qspi_writel(q, LUT0(FSL_WRITE, PAD1, 0),
|
||||
base + QUADSPI_LUT(lut_base + 1));
|
||||
@ -432,10 +418,8 @@ static void fsl_qspi_init_lut(struct fsl_qspi *q)
|
||||
/* Erase a sector */
|
||||
lut_base = SEQID_SE * 4;
|
||||
|
||||
cmd = q->nor[0].erase_opcode;
|
||||
addrlen = q->nor_size <= SZ_16M ? ADDR24BIT : ADDR32BIT;
|
||||
|
||||
qspi_writel(q, LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen),
|
||||
qspi_writel(q, LUT0(CMD, PAD1, nor->erase_opcode) |
|
||||
LUT1(ADDR, PAD1, addrlen),
|
||||
base + QUADSPI_LUT(lut_base));
|
||||
|
||||
/* Erase the whole chip */
|
||||
@ -484,7 +468,7 @@ static int fsl_qspi_get_seqid(struct fsl_qspi *q, u8 cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case SPINOR_OP_READ_1_1_4:
|
||||
return SEQID_QUAD_READ;
|
||||
return SEQID_READ;
|
||||
case SPINOR_OP_WREN:
|
||||
return SEQID_WREN;
|
||||
case SPINOR_OP_WRDI:
|
||||
|
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
|
||||
* 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])
|
||||
@ -188,6 +198,78 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
||||
return mtd->priv;
|
||||
}
|
||||
|
||||
|
||||
static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
if (table[i][0] == opcode)
|
||||
return table[i][1];
|
||||
|
||||
/* No conversion found, keep input op code. */
|
||||
return opcode;
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_read(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_read[][2] = {
|
||||
{ SPINOR_OP_READ, SPINOR_OP_READ_4B },
|
||||
{ SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B },
|
||||
{ SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B },
|
||||
{ SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B },
|
||||
{ SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B },
|
||||
{ SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
|
||||
ARRAY_SIZE(spi_nor_3to4_read));
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_program(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_program[][2] = {
|
||||
{ SPINOR_OP_PP, SPINOR_OP_PP_4B },
|
||||
{ SPINOR_OP_PP_1_1_4, SPINOR_OP_PP_1_1_4_4B },
|
||||
{ SPINOR_OP_PP_1_4_4, SPINOR_OP_PP_1_4_4_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_program,
|
||||
ARRAY_SIZE(spi_nor_3to4_program));
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_erase(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_erase[][2] = {
|
||||
{ SPINOR_OP_BE_4K, SPINOR_OP_BE_4K_4B },
|
||||
{ SPINOR_OP_BE_32K, SPINOR_OP_BE_32K_4B },
|
||||
{ SPINOR_OP_SE, SPINOR_OP_SE_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase,
|
||||
ARRAY_SIZE(spi_nor_3to4_erase));
|
||||
}
|
||||
|
||||
static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
|
||||
const struct flash_info *info)
|
||||
{
|
||||
/* Do some manufacturer fixups first */
|
||||
switch (JEDEC_MFR(info)) {
|
||||
case SNOR_MFR_SPANSION:
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
nor->mtd.erasesize = info->sector_size;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
|
||||
nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
|
||||
nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
|
||||
}
|
||||
|
||||
/* Enable/disable 4-byte addressing mode. */
|
||||
static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
||||
int enable)
|
||||
@ -217,6 +299,21 @@ static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
||||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int s3an_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return !!(val & XSR_RDY);
|
||||
}
|
||||
|
||||
static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr = read_sr(nor);
|
||||
@ -238,7 +335,11 @@ static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
static int spi_nor_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr, fsr;
|
||||
sr = spi_nor_sr_ready(nor);
|
||||
|
||||
if (nor->flags & SNOR_F_READY_XSR_RDY)
|
||||
sr = s3an_sr_ready(nor);
|
||||
else
|
||||
sr = spi_nor_sr_ready(nor);
|
||||
if (sr < 0)
|
||||
return sr;
|
||||
fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1;
|
||||
@ -319,6 +420,27 @@ static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
|
||||
mutex_unlock(&nor->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* This code converts an address to the Default Address Mode, that has non
|
||||
* power of two page sizes. We must support this mode because it is the default
|
||||
* mode supported by Xilinx tools, it can access the whole flash area and
|
||||
* changing over to the Power-of-two mode is irreversible and corrupts the
|
||||
* original data.
|
||||
* Addr can safely be unsigned int, the biggest S3AN device is smaller than
|
||||
* 4 MiB.
|
||||
*/
|
||||
static loff_t spi_nor_s3an_addr_convert(struct spi_nor *nor, unsigned int addr)
|
||||
{
|
||||
unsigned int offset;
|
||||
unsigned int page;
|
||||
|
||||
offset = addr % nor->page_size;
|
||||
page = addr / nor->page_size;
|
||||
page <<= (nor->page_size > 512) ? 10 : 9;
|
||||
|
||||
return page | offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiate the erasure of a single sector
|
||||
*/
|
||||
@ -327,6 +449,9 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
|
||||
u8 buf[SPI_NOR_MAX_ADDR_WIDTH];
|
||||
int i;
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
if (nor->erase)
|
||||
return nor->erase(nor, addr);
|
||||
|
||||
@ -368,7 +493,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
return ret;
|
||||
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||
unsigned long timeout;
|
||||
|
||||
write_enable(nor);
|
||||
@ -782,6 +907,19 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
.addr_width = (_addr_width), \
|
||||
.flags = (_flags),
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff \
|
||||
}, \
|
||||
.id_len = 3, \
|
||||
.sector_size = (8*_page_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = _page_size, \
|
||||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR | SPI_S3AN,
|
||||
|
||||
/* NOTE: double check command sets and memory organization when you add
|
||||
* more nor chips. This current list focusses on newer chips, which
|
||||
* have been converging on command sets which including JEDEC ID.
|
||||
@ -821,7 +959,7 @@ static const struct flash_info spi_nor_ids[] = {
|
||||
{ "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
||||
/* ESMT */
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||
|
||||
/* Everspin */
|
||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
@ -832,6 +970,11 @@ static const struct flash_info spi_nor_ids[] = {
|
||||
{ "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||
|
||||
/* GigaDevice */
|
||||
{
|
||||
"gd25q16", INFO(0xc84015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
"gd25q32", INFO(0xc84016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
@ -1014,6 +1157,13 @@ static const struct flash_info spi_nor_ids[] = {
|
||||
{ "cat25c09", CAT25_INFO( 128, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25c17", CAT25_INFO( 256, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25128", CAT25_INFO(2048, 8, 64, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
|
||||
/* Xilinx S3AN Internal Flash */
|
||||
{ "3S50AN", S3AN_INFO(0x1f2200, 64, 264) },
|
||||
{ "3S200AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S400AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S700AN", S3AN_INFO(0x1f2500, 512, 264) },
|
||||
{ "3S1400AN", S3AN_INFO(0x1f2600, 512, 528) },
|
||||
{ },
|
||||
};
|
||||
|
||||
@ -1054,7 +1204,12 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
return ret;
|
||||
|
||||
while (len) {
|
||||
ret = nor->read(nor, from, len, buf);
|
||||
loff_t addr = from;
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
ret = nor->read(nor, addr, len, buf);
|
||||
if (ret == 0) {
|
||||
/* We shouldn't see 0-length reads */
|
||||
ret = -EIO;
|
||||
@ -1175,17 +1330,32 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
|
||||
for (i = 0; i < len; ) {
|
||||
ssize_t written;
|
||||
loff_t addr = to + i;
|
||||
|
||||
page_offset = (to + i) & (nor->page_size - 1);
|
||||
WARN_ONCE(page_offset,
|
||||
"Writing at offset %zu into a NOR page. Writing partial pages may decrease reliability and increase wear of NOR flash.",
|
||||
page_offset);
|
||||
/*
|
||||
* If page_size is a power of two, the offset can be quickly
|
||||
* calculated with an AND operation. On the other cases we
|
||||
* need to do a modulus operation (more expensive).
|
||||
* Power of two numbers have only one bit set and we can use
|
||||
* the instruction hweight32 to detect if we need to do a
|
||||
* modulus (do_div()) or not.
|
||||
*/
|
||||
if (hweight32(nor->page_size) == 1) {
|
||||
page_offset = addr & (nor->page_size - 1);
|
||||
} else {
|
||||
uint64_t aux = addr;
|
||||
|
||||
page_offset = do_div(aux, nor->page_size);
|
||||
}
|
||||
/* the size of data remaining on the first page */
|
||||
page_remain = min_t(size_t,
|
||||
nor->page_size - page_offset, len - i);
|
||||
|
||||
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
|
||||
addr = spi_nor_s3an_addr_convert(nor, addr);
|
||||
|
||||
write_enable(nor);
|
||||
ret = nor->write(nor, to + i, page_remain, buf + i);
|
||||
ret = nor->write(nor, addr, page_remain, buf + i);
|
||||
if (ret < 0)
|
||||
goto write_err;
|
||||
written = ret;
|
||||
@ -1216,6 +1386,9 @@ static int macronix_quad_enable(struct spi_nor *nor)
|
||||
val = read_sr(nor);
|
||||
if (val < 0)
|
||||
return val;
|
||||
if (val & SR_QUAD_EN_MX)
|
||||
return 0;
|
||||
|
||||
write_enable(nor);
|
||||
|
||||
write_sr(nor, val | SR_QUAD_EN_MX);
|
||||
@ -1312,6 +1485,47 @@ static int spi_nor_check(struct spi_nor *nor)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3an_nor_scan(const struct flash_info *info, struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
nor->erase_opcode = SPINOR_OP_XSE;
|
||||
nor->program_opcode = SPINOR_OP_XPP;
|
||||
nor->read_opcode = SPINOR_OP_READ;
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
/*
|
||||
* This flashes have a page size of 264 or 528 bytes (known as
|
||||
* Default addressing mode). It can be changed to a more standard
|
||||
* Power of two mode where the page size is 256/512. This comes
|
||||
* with a price: there is 3% less of space, the data is corrupted
|
||||
* and the page size cannot be changed back to default addressing
|
||||
* mode.
|
||||
*
|
||||
* The current addressing mode can be read from the XRDSR register
|
||||
* and should not be changed, because is a destructive operation.
|
||||
*/
|
||||
if (val & XSR_PAGESIZE) {
|
||||
/* Flash in Power of 2 mode */
|
||||
nor->page_size = (nor->page_size == 264) ? 256 : 512;
|
||||
nor->mtd.writebufsize = nor->page_size;
|
||||
nor->mtd.size = 8 * nor->page_size * info->n_sectors;
|
||||
nor->mtd.erasesize = 8 * nor->page_size;
|
||||
} else {
|
||||
/* Flash in Default addressing mode */
|
||||
nor->flags |= SNOR_F_S3AN_ADDR_DEFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
{
|
||||
const struct flash_info *info = NULL;
|
||||
@ -1359,6 +1573,14 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
|
||||
mutex_init(&nor->lock);
|
||||
|
||||
/*
|
||||
* Make sure the XSR_RDY flag is set before calling
|
||||
* spi_nor_wait_till_ready(). Xilinx S3AN share MFR
|
||||
* with Atmel spi-nor
|
||||
*/
|
||||
if (info->flags & SPI_S3AN)
|
||||
nor->flags |= SNOR_F_READY_XSR_RDY;
|
||||
|
||||
/*
|
||||
* Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
|
||||
* with the software protection bits set
|
||||
@ -1483,27 +1705,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
else if (mtd->size > 0x1000000) {
|
||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||
nor->addr_width = 4;
|
||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION) {
|
||||
/* Dedicated 4-byte command set */
|
||||
switch (nor->flash_read) {
|
||||
case SPI_NOR_QUAD:
|
||||
nor->read_opcode = SPINOR_OP_READ4_1_1_4;
|
||||
break;
|
||||
case SPI_NOR_DUAL:
|
||||
nor->read_opcode = SPINOR_OP_READ4_1_1_2;
|
||||
break;
|
||||
case SPI_NOR_FAST:
|
||||
nor->read_opcode = SPINOR_OP_READ4_FAST;
|
||||
break;
|
||||
case SPI_NOR_NORMAL:
|
||||
nor->read_opcode = SPINOR_OP_READ4;
|
||||
break;
|
||||
}
|
||||
nor->program_opcode = SPINOR_OP_PP_4B;
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE_4B;
|
||||
mtd->erasesize = info->sector_size;
|
||||
} else
|
||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
|
||||
info->flags & SPI_NOR_4B_OPCODES)
|
||||
spi_nor_set_4byte_opcodes(nor, info);
|
||||
else
|
||||
set_4byte(nor, info, 1);
|
||||
} else {
|
||||
nor->addr_width = 3;
|
||||
@ -1517,6 +1722,12 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
|
||||
nor->read_dummy = spi_nor_read_dummy_cycles(nor);
|
||||
|
||||
if (info->flags & SPI_S3AN) {
|
||||
ret = s3an_nor_scan(info, nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
||||
(long long)mtd->size >> 10);
|
||||
|
||||
|
@ -371,7 +371,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
||||
/* default mode, does not need flex_cmd */
|
||||
flex_mode = 0;
|
||||
else
|
||||
command = SPINOR_OP_READ4_FAST;
|
||||
command = SPINOR_OP_READ_FAST_4B;
|
||||
break;
|
||||
case SPI_NBITS_DUAL:
|
||||
bpc = 0x00000001;
|
||||
@ -384,7 +384,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
||||
} else {
|
||||
command = SPINOR_OP_READ_1_1_2;
|
||||
if (spans_4byte)
|
||||
command = SPINOR_OP_READ4_1_1_2;
|
||||
command = SPINOR_OP_READ_1_1_2_4B;
|
||||
}
|
||||
break;
|
||||
case SPI_NBITS_QUAD:
|
||||
@ -399,7 +399,7 @@ static int bcm_qspi_bspi_set_flex_mode(struct bcm_qspi *qspi, int width,
|
||||
} else {
|
||||
command = SPINOR_OP_READ_1_1_4;
|
||||
if (spans_4byte)
|
||||
command = SPINOR_OP_READ4_1_1_4;
|
||||
command = SPINOR_OP_READ_1_1_4_4B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -20,6 +20,8 @@
|
||||
#ifndef LPC_ICH_H
|
||||
#define LPC_ICH_H
|
||||
|
||||
#include <linux/platform_data/intel-spi.h>
|
||||
|
||||
/* GPIO resources */
|
||||
#define ICH_RES_GPIO 0
|
||||
#define ICH_RES_GPE0 1
|
||||
@ -40,6 +42,7 @@ struct lpc_ich_info {
|
||||
char name[32];
|
||||
unsigned int iTCO_version;
|
||||
unsigned int gpio_version;
|
||||
enum intel_spi_type spi_type;
|
||||
u8 use_gpio;
|
||||
};
|
||||
|
||||
|
@ -43,9 +43,13 @@
|
||||
#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */
|
||||
#define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad SPI) */
|
||||
#define SPINOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual Output SPI) */
|
||||
#define SPINOR_OP_READ_1_2_2 0xbb /* Read data bytes (Dual I/O SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad Output SPI) */
|
||||
#define SPINOR_OP_READ_1_4_4 0xeb /* Read data bytes (Quad I/O SPI) */
|
||||
#define SPINOR_OP_PP 0x02 /* Page program (up to 256 bytes) */
|
||||
#define SPINOR_OP_PP_1_1_4 0x32 /* Quad page program */
|
||||
#define SPINOR_OP_PP_1_4_4 0x38 /* Quad page program */
|
||||
#define SPINOR_OP_BE_4K 0x20 /* Erase 4KiB block */
|
||||
#define SPINOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */
|
||||
#define SPINOR_OP_BE_32K 0x52 /* Erase 32KiB block */
|
||||
@ -56,11 +60,17 @@
|
||||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||
|
||||
/* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
|
||||
#define SPINOR_OP_READ4 0x13 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ4_FAST 0x0c /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ4_1_1_2 0x3c /* Read data bytes (Dual SPI) */
|
||||
#define SPINOR_OP_READ4_1_1_4 0x6c /* Read data bytes (Quad SPI) */
|
||||
#define SPINOR_OP_READ_4B 0x13 /* Read data bytes (low frequency) */
|
||||
#define SPINOR_OP_READ_FAST_4B 0x0c /* Read data bytes (high frequency) */
|
||||
#define SPINOR_OP_READ_1_1_2_4B 0x3c /* Read data bytes (Dual Output SPI) */
|
||||
#define SPINOR_OP_READ_1_2_2_4B 0xbc /* Read data bytes (Dual I/O SPI) */
|
||||
#define SPINOR_OP_READ_1_1_4_4B 0x6c /* Read data bytes (Quad Output SPI) */
|
||||
#define SPINOR_OP_READ_1_4_4_4B 0xec /* Read data bytes (Quad I/O SPI) */
|
||||
#define SPINOR_OP_PP_4B 0x12 /* Page program (up to 256 bytes) */
|
||||
#define SPINOR_OP_PP_1_1_4_4B 0x34 /* Quad page program */
|
||||
#define SPINOR_OP_PP_1_4_4_4B 0x3e /* Quad page program */
|
||||
#define SPINOR_OP_BE_4K_4B 0x21 /* Erase 4KiB block */
|
||||
#define SPINOR_OP_BE_32K_4B 0x5c /* Erase 32KiB block */
|
||||
#define SPINOR_OP_SE_4B 0xdc /* Sector erase (usually 64KiB) */
|
||||
|
||||
/* Used for SST flashes only. */
|
||||
@ -68,6 +78,15 @@
|
||||
#define SPINOR_OP_WRDI 0x04 /* Write disable */
|
||||
#define SPINOR_OP_AAI_WP 0xad /* Auto address increment word program */
|
||||
|
||||
/* Used for S3AN flashes only */
|
||||
#define SPINOR_OP_XSE 0x50 /* Sector erase */
|
||||
#define SPINOR_OP_XPP 0x82 /* Page program */
|
||||
#define SPINOR_OP_XRDSR 0xd7 /* Read status register */
|
||||
|
||||
#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */
|
||||
#define XSR_RDY BIT(7) /* Ready */
|
||||
|
||||
|
||||
/* Used for Macronix and Winbond flashes. */
|
||||
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
|
||||
#define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
|
||||
@ -119,6 +138,9 @@ enum spi_nor_ops {
|
||||
enum spi_nor_option_flags {
|
||||
SNOR_F_USE_FSR = BIT(0),
|
||||
SNOR_F_HAS_SR_TB = BIT(1),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||
SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
|
||||
SNOR_F_READY_XSR_RDY = BIT(4),
|
||||
};
|
||||
|
||||
/**
|
||||
|
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…
Reference in New Issue
Block a user