Hi Naga, Le 05/12/2016 à 08:02, Naga Sureshkumar Relli a écrit : > Hi Cyrille, > >>> Hi Cyrille, >>> >>>> I have not finished to review the whole series yet but here some >>>> first >>>> comments: >>> >>> Thanks for reviewing these patch series. >>> >>>> >>>> Le 27/11/2016 à 09:33, Naga Sureshkumar Relli a écrit : >>>>> This patch adds stripe support and it is needed for GQSPI parallel >>>>> configuration mode by: >>>>> >>>>> - Adding required parameters like stripe and shift to spi_nor >>>>> structure. >>>>> - Initializing all added parameters in spi_nor_scan() >>>>> - Updating read_sr() and read_fsr() for getting status from both >>>>> flashes >>>>> - Increasing page_size, sector_size, erase_size and toatal flash >>>>> size as and when required. >>>>> - Dividing address by 2 >>>>> - Updating spi->master->flags for qspi driver to change CS >>>>> >>>>> Signed-off-by: Naga Sureshkumar Relli <nagas...@xilinx.com> >>>>> --- >>>>> Changes for v4: >>>>> - rename isparallel to stripe >>>>> Changes for v3: >>>>> - No change >>>>> Changes for v2: >>>>> - Splitted to separate MTD layer changes from SPI core changes >>>>> --- >>>>> drivers/mtd/spi-nor/spi-nor.c | 130 >>>> ++++++++++++++++++++++++++++++++---------- >>>>> include/linux/mtd/spi-nor.h | 2 + >>>>> 2 files changed, 103 insertions(+), 29 deletions(-) >>>>> >>>>> diff --git a/drivers/mtd/spi-nor/spi-nor.c >>>>> b/drivers/mtd/spi-nor/spi-nor.c index d0fc165..4252239 100644 >>>>> --- a/drivers/mtd/spi-nor/spi-nor.c >>>>> +++ b/drivers/mtd/spi-nor/spi-nor.c >>>>> @@ -22,6 +22,7 @@ >>>>> #include <linux/of_platform.h> >>>>> #include <linux/spi/flash.h> >>>>> #include <linux/mtd/spi-nor.h> >>>>> +#include <linux/spi/spi.h> >>>>> >>>>> /* Define max times to check status register before we give up. */ >>>>> >>>>> @@ -89,15 +90,24 @@ static const struct flash_info >>>>> *spi_nor_match_id(const char *name); static int read_sr(struct >>>>> spi_nor *nor) { >>>>> int ret; >>>>> - u8 val; >>>>> + u8 val[2]; >>>>> >>>>> - ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val, 1); >>>>> - if (ret < 0) { >>>>> - pr_err("error %d reading SR\n", (int) ret); >>>>> - return ret; >>>>> + if (nor->stripe) { >>>>> + ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val[0], 2); >>>>> + if (ret < 0) { >>>>> + pr_err("error %d reading SR\n", (int) ret); >>>>> + return ret; >>>>> + } >>>>> + val[0] |= val[1]; >>>> Why '|' rather than '&' ? >>>> I guess because of the 'Write In Progress/Busy' bit: when called by >>>> spi_nor_sr_ready(), you want to be sure that this 'BUSY' bit is >>>> cleared on both memories. >>>> >>>> But what about when the Status Register is read for purpose other >>>> than checking the state of the 'BUSY' bit? >>>> >>> Yes you are correct, I will change this. >>> >>>> What about SPI controllers supporting more than 2 memories in parallel? >>>> >>>> This solution might fit the ZynqMP controller but doesn't look so generic. >>>> >>>>> + } else { >>>>> + ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val[0], 1); >>>>> + if (ret < 0) { >>>>> + pr_err("error %d reading SR\n", (int) ret); >>>>> + return ret; >>>>> + } >>>>> } >>>>> >>>>> - return val; >>>>> + return val[0]; >>>>> } >>>>> >>>>> /* >>>>> @@ -108,15 +118,24 @@ static int read_sr(struct spi_nor *nor) >>>>> static int read_fsr(struct spi_nor *nor) { >>>>> int ret; >>>>> - u8 val; >>>>> + u8 val[2]; >>>>> >>>>> - ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val, 1); >>>>> - if (ret < 0) { >>>>> - pr_err("error %d reading FSR\n", ret); >>>>> - return ret; >>>>> + if (nor->stripe) { >>>>> + ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val[0], 2); >>>>> + if (ret < 0) { >>>>> + pr_err("error %d reading FSR\n", ret); >>>>> + return ret; >>>>> + } >>>>> + val[0] &= val[1]; >>>> Same comment here: why '&' rather than '|'? >>>> Surely because of the the 'READY' bit which should be set for both >> memories. >>> I will update this also. >>>> >>>>> + } else { >>>>> + ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val[0], 1); >>>>> + if (ret < 0) { >>>>> + pr_err("error %d reading FSR\n", ret); >>>>> + return ret; >>>>> + } >>>>> } >>>>> >>>>> - return val; >>>>> + return val[0]; >>>>> } >>>>> >>>>> /* >>>>> @@ -290,9 +309,16 @@ static int spi_nor_wait_till_ready(struct >>>>> spi_nor >>>> *nor) >>>>> */ >>>>> static int erase_chip(struct spi_nor *nor) { >>>>> + u32 ret; >>>>> + >>>>> dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10)); >>>>> >>>>> - return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0); >>>>> + ret = nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0); >>>>> + if (ret) >>>>> + return ret; >>>>> + >>>>> + return ret; >>>>> + >>>> >>>> if (ret) >>>> return ret; >>>> else >>>> return ret; >>>> >>>> This chunk should be removed, it doesn't ease the patch review ;) >>> Ok, I will remove. >>>> >>>>> } >>>>> >>>>> static int spi_nor_lock_and_prep(struct spi_nor *nor, enum >>>>> spi_nor_ops ops) @@ -349,7 +375,7 @@ static int >>>>> spi_nor_erase_sector(struct spi_nor *nor, u32 addr) static int >>>>> spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) { >>>>> struct spi_nor *nor = mtd_to_spi_nor(mtd); >>>>> - u32 addr, len; >>>>> + u32 addr, len, offset; >>>>> uint32_t rem; >>>>> int ret; >>>>> >>>>> @@ -399,9 +425,13 @@ static int spi_nor_erase(struct mtd_info *mtd, >>>> struct erase_info *instr) >>>>> /* "sector"-at-a-time erase */ >>>>> } else { >>>>> while (len) { >>>>> + >>>>> write_enable(nor); >>>>> + offset = addr; >>>>> + if (nor->stripe) >>>>> + offset /= 2; >>>> >>>> I guess this should be /= 4 for controllers supporting 4 memories in >> parallel. >>>> Shouldn't you use nor->shift and define shift as an unsigned int >>>> instead of a bool? >>>> offset >>= nor->shift; >>>> >>> Yes we can use this shift, I will update >>> >>>> Anyway, by tuning the address here in spi-nor.c rather than in the >>>> SPI controller driver, you impose a model to support parallel >>>> memories that might not be suited to other controllers. >>> >>> For this ZynqMP GQSPI controller parallel configuration, globally >>> spi-nor should know about this stripe feature And based on that address >> has to change. >>> As I mentioned in cover letter, this controller in parallel configuration >>> will >> work with even addresses only. >>> i.e. Before creating address format(m25p_addr2cmd) in mtd layer, spi-nor >> should change that address based on stripe option. >>> >>> I am updating this offset based on stripe option, and stripe option will >> update by reading dt property in nor_scan(). >>> So the controller which doesn't support, then the stripe will be zero. >>> Or Can you please suggest any other way? >>> >>>> >>>>> >>>>> - ret = spi_nor_erase_sector(nor, addr); >>>>> + ret = spi_nor_erase_sector(nor, offset); >>>>> if (ret) >>>>> goto erase_err; >>>>> >>>>> @@ -525,6 +555,8 @@ static int stm_lock(struct spi_nor *nor, loff_t >>>>> ofs, >>>> uint64_t len) >>>>> bool use_top; >>>>> int ret; >>>>> >>>>> + ofs = ofs >> nor->shift; >>>>> + >>>>> status_old = read_sr(nor); >>>>> if (status_old < 0) >>>>> return status_old; >>>>> @@ -610,6 +642,8 @@ static int stm_unlock(struct spi_nor *nor, >>>>> loff_t ofs, >>>> uint64_t len) >>>>> bool use_top; >>>>> int ret; >>>>> >>>>> + ofs = ofs >> nor->shift; >>>>> + >>>>> status_old = read_sr(nor); >>>>> if (status_old < 0) >>>>> return status_old; >>>>> @@ -709,6 +743,8 @@ static int spi_nor_lock(struct mtd_info *mtd, >>>>> loff_t >>>> ofs, uint64_t len) >>>>> if (ret) >>>>> return ret; >>>>> >>>>> + ofs = ofs >> nor->shift; >>>>> + >>>>> ret = nor->flash_lock(nor, ofs, len); >>>>> >>>>> spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); @@ - >>>> 724,6 +760,8 >>>>> @@ static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, >>>>> uint64_t >>>> len) >>>>> if (ret) >>>>> return ret; >>>>> >>>>> + ofs = ofs >> nor->shift; >>>>> + >>>>> ret = nor->flash_unlock(nor, ofs, len); >>>>> >>>>> spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); @@ - >>>> 1018,6 +1056,9 >>>>> @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) >>>>> u8 id[SPI_NOR_MAX_ID_LEN]; >>>>> const struct flash_info *info; >>>>> >>>>> + nor->spi->master->flags &= ~(SPI_MASTER_BOTH_CS | >>>>> + SPI_MASTER_DATA_STRIPE); >>>>> + >>>>> tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, >>>> SPI_NOR_MAX_ID_LEN); >>>>> if (tmp < 0) { >>>>> dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp); >>>> @@ -1041,6 >>>>> +1082,7 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t >>>>> +from, >>>>> size_t len, { >>>>> struct spi_nor *nor = mtd_to_spi_nor(mtd); >>>>> int ret; >>>>> + u32 offset = from; >>>>> >>>>> dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len); >>>>> >>>>> @@ -1049,7 +1091,13 @@ 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); >>>>> + >>>>> + offset = from; >>>>> + >>>>> + if (nor->stripe) >>>>> + offset /= 2; >>>>> + >>>>> + ret = nor->read(nor, offset, len, buf); >>>>> if (ret == 0) { >>>>> /* We shouldn't see 0-length reads */ >>>>> ret = -EIO; >>>>> @@ -1161,6 +1209,7 @@ static int spi_nor_write(struct mtd_info *mtd, >>>> loff_t to, size_t len, >>>>> struct spi_nor *nor = mtd_to_spi_nor(mtd); >>>>> size_t page_offset, page_remain, i; >>>>> ssize_t ret; >>>>> + u32 offset; >>>>> >>>>> dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len); >>>>> >>>>> @@ -1178,9 +1227,13 @@ static int spi_nor_write(struct mtd_info >>>>> *mtd, >>>> loff_t to, size_t len, >>>>> /* the size of data remaining on the first page */ >>>>> page_remain = min_t(size_t, >>>>> nor->page_size - page_offset, len - i); >>>>> + offset = (to + i); >>>>> + >>>>> + if (nor->stripe) >>>>> + offset /= 2; >>>>> >>>>> write_enable(nor); >>>>> - ret = nor->write(nor, to + i, page_remain, buf + i); >>>>> + ret = nor->write(nor, (offset), page_remain, buf + i); >>>>> if (ret < 0) >>>>> goto write_err; >>>>> written = ret; >>>>> @@ -1302,22 +1355,22 @@ static int spi_nor_check(struct spi_nor >>>>> *nor) >>>>> >>>>> int spi_nor_scan(struct spi_nor *nor, const char *name, enum >>>>> read_mode mode) { >>>>> - const struct flash_info *info = NULL; >>>>> + struct flash_info *info = NULL; >>>> >>>> You should not remove the const and should not try to modify members >>>> of *info. >>>> >>>>> struct device *dev = nor->dev; >>>>> struct mtd_info *mtd = &nor->mtd; >>>>> struct device_node *np = spi_nor_get_flash_node(nor); >>>>> - int ret; >>>>> - int i; >>>>> + struct device_node *np_spi; >>>>> + int ret, i, xlnx_qspi_mode; >>>>> >>>>> ret = spi_nor_check(nor); >>>>> if (ret) >>>>> return ret; >>>>> >>>>> if (name) >>>>> - info = spi_nor_match_id(name); >>>>> + info = (struct flash_info *)spi_nor_match_id(name); >>>>> /* Try to auto-detect if chip name wasn't specified or not found */ >>>>> if (!info) >>>>> - info = spi_nor_read_id(nor); >>>>> + info = (struct flash_info *)spi_nor_read_id(nor); >>>>> if (IS_ERR_OR_NULL(info)) >>>>> return -ENOENT; >>>>> >>>> Both spi_nor_match_id() and spi_nor_read_id(), when they succeed, >>>> return a pointer to an entry of the spi_nor_ids[] array, which is >>>> located in a read- only memory area. >>>> >>>> Since your patch doesn't remove the const attribute of the >>>> spi_nor_ids[], I wonder whether it has been tested. I expect it not >>>> to work on most architecture. >>>> >>>> Anyway spi_nor_ids[] should remain const. Let's take the case of >>>> eXecution In Place (XIP) from an external memory: if spi_nor_ids[] is >>>> const, it can be read directly from this external (read-only) memory >>>> and we never need to copy the array in RAM, so we save some KB of >> RAM. >>>> This is just an example but I guess we can find other reasons to keep >>>> this array const. >>>> >>>>> @@ -1341,7 +1394,7 @@ int spi_nor_scan(struct spi_nor *nor, const >>>>> char >>>> *name, enum read_mode mode) >>>>> */ >>>>> dev_warn(dev, "found %s, expected %s\n", >>>>> jinfo->name, info->name); >>>>> - info = jinfo; >>>>> + info = (struct flash_info *)jinfo; >>>>> } >>>>> } >>>>> >>>>> @@ -1370,6 +1423,27 @@ int spi_nor_scan(struct spi_nor *nor, const >>>>> char >>>> *name, enum read_mode mode) >>>>> mtd->size = info->sector_size * info->n_sectors; >>>>> mtd->_erase = spi_nor_erase; >>>>> mtd->_read = spi_nor_read; >>>>> +#ifdef CONFIG_OF >>>>> + np_spi = of_get_next_parent(np); >>>>> + >>>>> + if (of_property_read_u32(np_spi, "xlnx,qspi-mode", >>>>> + &xlnx_qspi_mode) < 0) { >>>> This really looks controller specific so should not be placed in the >>>> generic spi- nor.c file. >>> >>> Const is removed in info struct, because to update info members based >> parallel configuration. >>> As I mentioned above, for this parallel configuration, mtd and >>> spi-nor should know the details like >>> mtd->size, info->sectors, sector_size and page_size. >> >> You can tune the values of nor->mtd.size, nor->mtd.erasesize, nor- >>> page_size or whatever member of nor/nor.mtd as needed without ever >> modifying members of *info. >> >> If you modify *info then spi_nor_scan() is called a 2nd time to probe and >> configure SPI memories of the same part but connected to another >> controller, the values of the modified members in this *info would not be >> those expected. >> So *info and the spi_nor_ids[] array must remain const. >> >> The *info structure is not used outside spi_nor_scan(); none of >> spi_nor_read(), >> spi_nor_write() and spi_nor_erase() refers to *info hence every relevant >> value can be set only nor or nor->mtd members. >> >> >> Anyway, I think OR'ing or AND'ing values of memory registers depending on >> the relevant bit we want to check is not the right solution. >> If done in spi-nor.c, there would be a specific case for each memory register >> we read, each register bit would have to be handled differently. >> >> spi-nor.c tries to support as much memory parts as possible, it deals with >> many registers and bits: Status/Control registers, Quad Enable bits... >> >> If we start to OR or AND each of these register values to support the >> stripping mode, spi-nor will become really hard to maintain. >> >> I don't know whether it could be done with the xilinx controller but I >> thought >> about first configuring the two memories independently calling >> spi_nor_scan() twice; one call for each memory. >> >> Then the xilinx driver could register only one struct mtd_info, overriding >> mtd->_read() [and likely mtd->_write() and mtd->_erase() too] set by >> spi_nor_scan() with a xilinx driver custom implementation so this driver >> supports its controller stripping mode as it wants. >> >> Of course, this solution assumes that the SPI controller has one dedicated >> chip-select line for each memory and not a single chip-select line shared by >> both memories. The memories should be configured independently: you >> can't assume multiple instances of the very same memory part always return >> the exact same value when reading one of their register. Logical AND/OR is >> not a generic solution, IMHO. >> >> If the xilinx controller has only one shared chip-select line then let's see >> whether 2 GPIO lines could be used as independent chip-select lines. >> >> > In parallel configuration, Physically we have two flashes but mtd will see as > single flash memory (sum of both memories) > If we call spi_nor_scan(), twice then read/write will override but > nor->mtd.size, nor->mtd.erasesize, nor->page_size will remain same, I,e they > will also override, they won't append. > I tried calling spi_nor_scan() twice by some hacks but nor->mtd.size, > nor->mtd.erasesize, nor->page_size are not changing > Also the same issue we are getting for flash address, need to shift the > address to work in this configuration. > Also to tune nor->mtd.size, nor->mtd.erasesize, nor->page_size, we need to > touch this spi-nor.c > > Please kindly suggest, if I am wrong. >
What I've been suggesting is: { struct spi_nor *nor1, *nor2; struct mtd_info *mtd; enum read_mode mode = SPI_NOR_QUAD; int err; [...] err = spi_nor_scan(nor1, NULL, mode); if (err) return err; /* or handle error properly. */ err = spi_nor_scan(nor2, NULL, mode); if (err) return err; mtd = &nor1->mtd; mtd->erasesize <<= 1; mtd->size <<= 1; mtd->writebufsize <<= 1; nor1->page_size <<= 1; /* tune all other relevant members of nor1/mtd. */ /* override relevant mtd hooks. */ mtd->_read = stripping_read; mtd->_erase = stripping_erase; mtd->_write = stripping_write; mtd->_lock = ...; mtd->_unlock = ...; mtd->_is_lock = ...; /* register a single mtd_info structure. */ err = mtd_device_register(mtd, NULL, 0); if (err) return err; [...] } Best regards, Cyrille > Thanks, > Naga Sureshkumar Relli > >> Best regards, >> >> Cyrille > > > This email and any attachments are intended for the sole use of the named > recipient(s) and contain(s) confidential information that may be proprietary, > privileged or copyrighted under applicable law. If you are not the intended > recipient, do not read, copy, or forward this email message or any > attachments. Delete this email message and any attachments immediately. > >