From patchwork Wed Jun 26 07:41:12 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Poddar, Sourav" X-Patchwork-Id: 254593 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from casper.infradead.org (casper.infradead.org [IPv6:2001:770:15f::2]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id A24C02C0087 for ; Wed, 26 Jun 2013 17:43:23 +1000 (EST) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UrkNN-0008E6-UD; Wed, 26 Jun 2013 07:42:50 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UrkNC-0005gp-7w; Wed, 26 Jun 2013 07:42:38 +0000 Received: from comal.ext.ti.com ([198.47.26.152]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UrkN2-0005df-2R for linux-mtd@lists.infradead.org; Wed, 26 Jun 2013 07:42:30 +0000 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id r5Q7fkQ2032746; Wed, 26 Jun 2013 02:41:46 -0500 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id r5Q7fjUv025535; Wed, 26 Jun 2013 02:41:45 -0500 Received: from dlelxv22.itg.ti.com (172.17.1.197) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.2.342.3; Wed, 26 Jun 2013 02:41:45 -0500 Received: from a0131647.apr.dhcp.ti.com (a0131647.apr.dhcp.ti.com [172.24.145.168]) by dlelxv22.itg.ti.com (8.13.8/8.13.8) with ESMTP id r5Q7fP1Z021398; Wed, 26 Jun 2013 02:41:42 -0500 From: Sourav Poddar To: , , , , , , Subject: [PATCH 3/3] drivers: mtd: spinand: Add qspi spansion flash controller Date: Wed, 26 Jun 2013 13:11:12 +0530 Message-ID: <1372232472-2641-4-git-send-email-sourav.poddar@ti.com> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1372232472-2641-1-git-send-email-sourav.poddar@ti.com> References: <1372232472-2641-1-git-send-email-sourav.poddar@ti.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130626_034228_347751_4CFE28D7 X-CRM114-Status: GOOD ( 23.62 ) X-Spam-Score: -8.2 (--------) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-8.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -5.0 RCVD_IN_DNSWL_HI RBL: Sender listed at http://www.dnswl.org/, high trust [198.47.26.152 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -1.3 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Sourav Poddar , linux-omap@vger.kernel.org, rnayak@ti.com, linux-kernel@vger.kernel.org, balbi@ti.com X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org The patch adds support for spansion s25fl256s spi flash controller. Currently, the patch supports only SPI based transaction. As, the qspi to which flash is attached supports memory mapped interface, support will be added in future for memory mapped transactions also. This driver gets attached to the generic spinand mtd framework proposed in the first patch of the series. Signed-off-by: Sourav Poddar --- drivers/mtd/spinand/Kconfig | 7 + drivers/mtd/spinand/Makefile | 2 +- drivers/mtd/spinand/ti-qspi-flash.c | 373 +++++++++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 1 deletions(-) create mode 100644 drivers/mtd/spinand/ti-qspi-flash.c diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig index 38c739f..1342de3 100644 --- a/drivers/mtd/spinand/Kconfig +++ b/drivers/mtd/spinand/Kconfig @@ -16,6 +16,13 @@ config MTD_SPINAND_ONDIEECC help Internel ECC +config MTD_S25FL256S + tristate "Support spansion memory mapped SPI Flash chips" + depends on SPI_MASTER + help + This enables access to spansion QSPI flash chips, which used + memory mapped interface used for program and data storage. + config MTD_SPINAND_SWECC bool "Use software ECC" depends on MTD_NAND diff --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile index 355e726..8ad0dd5 100644 --- a/drivers/mtd/spinand/Makefile +++ b/drivers/mtd/spinand/Makefile @@ -5,6 +5,6 @@ # Core functionality. obj-$(CONFIG_MTD_SPINAND) += spinand.o -spinand-objs := spinand_mtd.o spinand_lld.o +spinand-objs := spinand_mtd.o spinand_lld.o ti-qspi-flash.o diff --git a/drivers/mtd/spinand/ti-qspi-flash.c b/drivers/mtd/spinand/ti-qspi-flash.c new file mode 100644 index 0000000..dfa6235 --- /dev/null +++ b/drivers/mtd/spinand/ti-qspi-flash.c @@ -0,0 +1,373 @@ +/* + * MTD SPI driver for spansion s25fl256s (and similar) serial flash chips + * + * Author: Sourav Poddar, sourav.poddar@ti.com + * + * Copyright (c) 2013, Texas Instruments. + * + * This code 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define CMD_OPCODE_RDSR 0x05 /* Read status register */ +#define CMD_OPCODE_FAST_READ 0x0b /* Fast Read */ +#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */ + +#define SR_WIP 1 /* Write in progress */ +#define SR_WEL 2 /* Write enable latch */ + +static u16 addr_width; +bool fast_read; + +static struct nand_ecclayout spinand_oob_0 = { + .eccbytes = 0, + .eccpos = {}, + .oobavail = 0, + .oobfree = { + {.offset = 0, + .length = 0}, } +}; + +/* + * Read the status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. +*/ +static int read_sr(struct spi_device *spi_nand) +{ + ssize_t retval; + u8 val; + u8 code = CMD_OPCODE_RDSR; + + retval = spi_write_then_read(spi_nand, &code, 1, &val, 1); + + if (retval < 0) { + dev_info(&spi_nand->dev, "error %d reading SR\n", + (int) retval); + return retval; + } + + return val; +} + +/* + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. +*/ +static inline int write_enable(struct spi_device *spi_nand) +{ + u8 code = CMD_WR_ENABLE; + + return spi_write_then_read(spi_nand, &code, 1, NULL, 0); +} + +/* + * Send write disble instruction to the chip. +*/ +static inline int write_disable(struct spi_device *spi_nand) +{ + u8 code = CMD_WR_DISABLE; + + return spi_write_then_read(spi_nand, &code, 1, NULL, 0); +} + +/* + * Service routine to read status register until ready, or timeout occurs. + * Returns non-zero if error. +*/ +static int wait_till_ready(struct spi_device *spi_nand) +{ + unsigned long deadline; + int sr; + + deadline = jiffies + MAX_READY_WAIT_JIFFIES; + + do { + sr = read_sr(spi_nand); + if (sr < 0) + return -1; + else if (!(sr & SR_WIP)) + break; + + cond_resched(); + } while (!time_after_eq(jiffies, deadline)); + + if ((sr & SR_WIP) == 0) + return 0; + + return -1; +} + +static inline int spinand_read_id(struct spi_device *spi_nand, u8 *id) +{ + u8 code = CMD_READ_ID; + + return spi_write_then_read(spi_nand, &code, 1, id, sizeof(id)); +} + +static void s25fl_addr2cmd(struct spi_device *spi_nand, + unsigned int addr, u8 *cmd) +{ + /* opcode is in cmd[0] */ + cmd[1] = addr >> (addr_width * 8 - 8); + cmd[2] = addr >> (addr_width * 8 - 16); + cmd[3] = addr >> (addr_width * 8 - 24); +} + +static int s25fl_cmdsz(struct spi_device *spi_nand) +{ + return 1 + addr_width; +} + +static int spinand_erase_block(struct spi_device *spi_nand, + struct spinand_info *info, u16 block_id) +{ + unsigned int offset; + u8 cmd[4]; + uint8_t opcode; + + offset = block_id * info->block_size; + + /* Wait until finished previous write command. */ + if (wait_till_ready(spi_nand)) + return 1; + + /* Send write enable, then erase commands. */ + write_enable(spi_nand); + + /* Set up command buffer. */ + opcode = CMD_ERASE_BLK; + cmd[0] = opcode; + s25fl_addr2cmd(spi_nand, offset, cmd); + + spi_write(spi_nand, cmd, s25fl_cmdsz(spi_nand)); + + return 0; +} + +static int spinand_read_page(struct spi_device *spi_nand, + struct spinand_info *info, u16 page_id, u16 offset, u16 len, u8 *rbuf) +{ + struct spi_transfer t[2]; + struct spi_message m; + uint8_t opcode; + u8 cmd[4]; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = s25fl_cmdsz(spi_nand); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = rbuf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + /* Wait till previous write/erase is done. */ + if (wait_till_ready(spi_nand)) + return 1; + + /* Set up the write data buffer. */ + opcode = fast_read ? CMD_OPCODE_FAST_READ : CMD_READ_RDM; + cmd[0] = opcode; + + s25fl_addr2cmd(spi_nand, offset, cmd); + + spi_sync(spi_nand, &m); + + return 0; +} + +static int spinand_program_page(struct spi_device *spi_nand, + struct spinand_info *info, u16 page_id, u16 offset, u16 len, u8 *wbuf) +{ + struct spi_transfer t[2]; + struct spi_message m; + u8 cmd[4]; + + pr_debug("%s: %s to 0x%08x, len %zd\n", dev_name(&spi_nand->dev), + __func__, (u32)offset, len); + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = 4; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = wbuf; + t[0].len = len; + spi_message_add_tail(&t[1], &m); + + write_enable(spi_nand); + + /* Wait until finished previous write command. */ + if (wait_till_ready(spi_nand)) + return 1; + + /* Set up the opcode in the write buffer. */ + cmd[0] = CMD_PROG_PAGE_CLRCACHE; + s25fl_addr2cmd(spi_nand, offset, cmd); + + spi_sync(spi_nand, &m); + + return 0; +} + +static int spinand_get_info(struct spi_device *spi_nand, + struct spinand_info *info, u8 *id) +{ + if (id[0] == 0x01 && id[1] == 0x02) { + info->mid = id[0]; + info->did = id[1]; + info->name = "S25FL256S"; + info->nand_size = (1024 * 32 * 1024); + info->page_size = 256; + info->page_main_size = 256; + info->page_spare_size = info->page_size - info->page_main_size; + info->block_size = (1024 * 64); + info->page_num_per_block = info->block_size / info->page_size; + info->block_main_size = info->page_main_size * + info->page_num_per_block; + info->usable_size = (1024 * 30 * 1024); + info->block_num_per_chip = info->nand_size / info->block_size; + info->block_shift = 16; + info->block_mask = info->block_size - 1; + info->page_shift = 8; + info->page_mask = info->page_size - 1; + info->ecclayout = &spinand_oob_0; + } + return 0; +} + +static int spinand_probe(struct spi_device *spi) +{ + ssize_t retval; + struct mtd_info *mtd; + struct spinand_chip *chip; + struct spinand_info *info; + struct mtd_part_parser_data ppdata; + struct device_node __maybe_unused *np = spi->dev.of_node; + + u8 id[2] = {0}; + + retval = spinand_read_id(spi, (u8 *)&id); + if (id[0] == 0 && id[1] == 0) { + pr_err(KERN_ERR "SPINAND: read id error! 0x%02x, 0x%02x!\n", + id[0], id[1]); + return 0; + } + + info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (np && of_property_read_bool(np, "s25fl,fast-read")) + fast_read = true; + if (np && of_property_read_bool(np, "s25fl,three-byte")) + addr_width = 3; + else + addr_width = 4; + + ppdata.of_node = spi->dev.of_node; + + retval = spinand_get_info(spi, info, (u8 *)&id); + + chip = kzalloc(sizeof(struct spinand_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->spi_nand = spi; + chip->info = info; + chip->read_id = spinand_read_id; + chip->read_page = spinand_read_page; + chip->program_page = spinand_program_page; + chip->erase_block = spinand_erase_block; + chip->buf = kzalloc(info->page_size, GFP_KERNEL); + if (!chip->buf) + return -ENOMEM; + + chip->oobbuf = kzalloc(info->ecclayout->oobavail, GFP_KERNEL); + if (!chip->oobbuf) + return -ENOMEM; + + mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, mtd); + + mtd->priv = chip; + + retval = spinand_mtd(mtd); + + return mtd_device_parse_register(mtd, NULL, &ppdata, + NULL, 1); +} + +/* + *spinand_remove--Remove the device driver + * @spi: the spi device. +*/ +static int spinand_remove(struct spi_device *spi) +{ + struct mtd_info *mtd; + struct spinand_chip *chip; + + mtd = dev_get_drvdata(&spi->dev); + + mtd_device_unregister(mtd); + + chip = mtd->priv; + + kfree(chip->info); + kfree(chip->buf); + kfree(chip->oobbuf); + kfree(chip); + kfree(mtd); + + return 0; +} + +static const struct of_device_id s25fl256s_dt_ids[] = { + { .compatible = "ti, s25fl256s"}, + { /* sentinel */ }, +}; + +static struct spi_driver spinand_driver = { + .driver = { + .name = "s25fl256s", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = s25fl256s_dt_ids, + }, + .probe = spinand_probe, + .remove = spinand_remove, +}; +module_spi_driver(spinand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sourav Poddar"); +MODULE_DESCRIPTION("MTD SPI driver for spansion flash chips");