From patchwork Wed Jan 23 08:01:08 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tony Prisk X-Patchwork-Id: 2022651 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id 9DA173FD86 for ; Wed, 23 Jan 2013 08:04:18 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TxvHY-0005GZ-UD; Wed, 23 Jan 2013 08:02:04 +0000 Received: from server.prisktech.co.nz ([115.188.14.127]) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TxvGf-0004tg-5h; Wed, 23 Jan 2013 08:01:16 +0000 Received: from localhost.localdomain (unknown [192.168.0.102]) by server.prisktech.co.nz (Postfix) with ESMTP id 44F6EFC0AB2; Wed, 23 Jan 2013 21:01:17 +1300 (NZDT) From: Tony Prisk To: dwmw2@infradead.org Subject: [PATCH v3 2/2] mtd: vt8500: Add support for Wondermedia Serial Flash Controller Date: Wed, 23 Jan 2013 21:01:08 +1300 Message-Id: <1358928068-12417-3-git-send-email-linux@prisktech.co.nz> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1358928068-12417-1-git-send-email-linux@prisktech.co.nz> References: <1358928068-12417-1-git-send-email-linux@prisktech.co.nz> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130123_030110_145227_F39C0B9C X-CRM114-Status: GOOD ( 28.40 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 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: dedekind1@gmail.com, linux-kernel@vger.kernel.org, Tony Prisk , linux-mtd@lists.infradead.org, vt8500-wm8505-linux-kernel@googlegroups.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org This patch adds support for the Wondermedia serial flash controller found on WM8505, WM8650 and WM8850 SoCs. Signed-off-by: Tony Prisk --- drivers/mtd/devices/Kconfig | 10 + drivers/mtd/devices/Makefile | 3 +- drivers/mtd/devices/wmt_sflash.c | 585 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 597 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/devices/wmt_sflash.c diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 27f80cd..dbabb08 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -128,6 +128,16 @@ config MTD_BCM47XXSFLASH registered by bcma as platform devices. This enables driver for serial flash memories (only read-only mode is implemented). +config MTD_WMT_SFLASH + tristate "WonderMedia Serial Flash Support" + depends on ARCH_VT8500 + help + Enable this option to provide support for the Wondermedia SoC serial + flash controller. + + Select M to build this driver as a module. The module will be called + 'wmt_sflash'. + config MTD_SLRAM tristate "Uncached system RAM" help diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 395733a..79af2f6 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o obj-$(CONFIG_MTD_M25P80) += m25p80.o obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o obj-$(CONFIG_MTD_SST25L) += sst25l.o +obj-$(CONFIG_MTD_WMT_SFLASH) += wmt_sflash.o flash_jedec.o obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o -CFLAGS_docg3.o += -I$(src) \ No newline at end of file +CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/wmt_sflash.c b/drivers/mtd/devices/wmt_sflash.c new file mode 100644 index 0000000..d3adf7a --- /dev/null +++ b/drivers/mtd/devices/wmt_sflash.c @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2012 Tony Prisk + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "flash_jedec.h" + + +/* controller only supports erase size of 64KB */ +#define WMT_ERASESIZE 0x10000 + +/* Serial Flash controller register offsets */ +#define SF_CHIP_SEL_0_CFG 0x000 +#define SF_CHIP_SEL_1_CFG 0x008 +#define SF_SPI_INTF_CFG 0x040 +#define SF_SPI_RD_WR_CTR 0x050 +#define SF_SPI_WR_EN_CTR 0x060 +#define SF_SPI_ER_CTR 0x070 +#define SF_SPI_ER_START_ADDR 0x074 +#define SF_SPI_ERROR_STATUS 0x080 +#define SF_SPI_MEM_0_SR_ACC 0x100 +#define SF_SPI_MEM_1_SR_ACC 0x110 +#define SF_SPI_PDWN_CTR_0 0x180 +#define SF_SPI_PDWN_CTR_1 0x190 +#define SF_SPI_PROG_CMD_CTR 0x200 +#define SF_SPI_USER_CMD_VAL 0x210 +#define SF_SPI_PROG_CMD_WBF 0x300 /* 64 bytes */ +#define SF_SPI_PROG_CMD_RBF 0x380 /* 64 bytes */ + +/* SF_SPI_WR_EN_CTR bit fields */ +#define SF_CS0_WR_EN BIT(0) +#define SF_CS1_WR_EN BIT(1) + +/* SF_SPI_ER_CTR bit fields */ +#define SF_SEC_ER_EN BIT(31) + +/* SF_SPI_ERROR_STATUS bit fields */ +#define SF_ERR_TIMEOUT BIT(31) +#define SF_ERR_WR_PROT_ERR BIT(5) +#define SF_ERR_MEM_REGION_ERR BIT(4) +#define SF_ERR_PWR_DWN_ACC_ERR BIT(3) +#define SF_ERR_PCMD_OP_ERR BIT(2) +#define SF_ERR_PCMD_ACC_ERR BIT(1) +#define SF_ERR_MASLOCK_ERR BIT(0) + +/* + * Serial Flash device manufacturers + * Please keep sorted by manufacturers ID + */ +#define MFR_SPANSION 0x01 +#define MFR_EON 0x1C +#define MFR_ATMEL 0x1F +#define MFR_NUMONYX 0x20 +#define MFR_FUDAN 0xA1 +#define MFR_SST 0xBF +#define MFR_MXIC 0xC2 +#define MFR_WINBOND 0xEF + +/* + * SF Device Models + * Please keep in the same order as the manufacturers table + */ + +/* Spansion */ +#define SPAN_FL016A 0x0214 /* 2 MB */ +#define SPAN_FL064A 0x0216 /* 8 MB */ + +/* Eon */ +#define EON_25P16 0x2015 /* 2 MB */ +#define EON_25P64 0x2017 /* 8 MB */ +#define EON_25F40 0x3113 /* 512 KB */ +#define EON_25F16 0x3115 /* 2 MB */ + +/* Atmel */ +#define AT_25DF041A 0x4401 /* 512KB */ + +/* Numonyx */ +#define NX_25P16 0x2015 /* 2 MB */ +#define NX_25P64 0x2017 /* 8 MB */ + +/* Fudan Microelectronics Group */ +#define FM_25F04 0x3113 /* 512 KB */ + +/* SST */ +#define SST_VF016B 0x2541 /* 2 MB */ + +/* MXIC */ +#define MX_L512 0x2010 /* 64 KB , 4KB*/ +#define MX_L4005A 0x2013 /* 512 KB */ +#define MX_L1605D 0x2015 /* 2 MB */ +#define MX_L3205D 0x2016 /* 4 MB */ +#define MX_L6405D 0x2017 /* 8 MB */ +#define MX_L1635D 0x2415 /* 2 MB */ +#define MX_L3235D 0x5E16 /* 4 MB */ +#define MX_L12805D 0x2018 /* 16 MB */ + +/* WinBond */ +#define WB_W25X40BV 0x3013 /* 512 KB */ +#define WB_X16A 0x3015 /* 2 MB */ +#define WB_X32 0x3016 /* 4 MB */ +#define WB_X64 0x3017 /* 8 MB */ + + +#define SF_ID(mfr, mdl) ((mfr << 16) | mdl) + +#define FLASH_UNKNOWN 0x00ffffff + +struct wmt_sf_chip { + u32 id; + u32 size; + u32 addr_phys; + u32 ccr; +}; + +struct wmt_sf_data { + struct mtd_info *sf_mtd; + struct clk *sf_clk; + struct device *dev; + + struct wmt_sf_chip chip[2]; + + void __iomem *base; /* register virt base */ + + void __iomem *sf_base_virt; /* mem-mapped sf virt base */ + u32 sf_base_phys; /* mem-mapped sf phys base */ + u32 sf_total_size; +}; + +static void sf_calc_ccr(struct wmt_sf_chip *chip) +{ + unsigned int cnt = 0, size; + + size = chip->size; + while (size) { + size >>= 1; + cnt++; + } + cnt -= 16; + cnt = cnt << 8; + chip->ccr = (chip->addr_phys | cnt); +} + +static int wmt_sf_init_hw(struct wmt_sf_data *info) +{ + u32 strap; + u32 phys_addr; + struct device_node *np; + struct flash_device_info *flash; + void __iomem *gpio_base; + + np = of_find_compatible_node(NULL, NULL, "wm,wm8650-gpio"); + if (!np) { + dev_err(info->dev, "Unable to find GPIO node\n"); + return -1; + } + + gpio_base = of_iomap(np, 0); + if (!gpio_base) { + dev_err(info->dev, "Failed to map gpio memory\n"); + return -1; + } + + strap = readl_relaxed(gpio_base + 0x100); + iounmap(gpio_base); + + if ((strap & 0x06) == 0) { + phys_addr = 0xFFFFFFFF; + writel(0x00000011, info->base + SF_SPI_RD_WR_CTR); + writel(0xFF800800, info->base + SF_CHIP_SEL_0_CFG); + writel(0x00030000, info->base + SF_SPI_INTF_CFG); + } else { + phys_addr = 0xEFFFFFFF; + writel(0x00000011, info->base + SF_SPI_RD_WR_CTR); + writel(0xEF800800, info->base + SF_CHIP_SEL_0_CFG); + writel(0x00030000, info->base + SF_SPI_INTF_CFG); + } + + info->chip[0].id = FLASH_UNKNOWN; + info->chip[1].id = FLASH_UNKNOWN; + + /* Read serial flash ID */ + writel(0x11, info->base + SF_SPI_RD_WR_CTR); + info->chip[0].id = readl(info->base + SF_SPI_MEM_0_SR_ACC); + writel(0x01, info->base + SF_SPI_RD_WR_CTR); + + writel(0x11, info->base + SF_SPI_RD_WR_CTR); + info->chip[1].id = readl(info->base + SF_SPI_MEM_1_SR_ACC); + writel(0x01, info->base + SF_SPI_RD_WR_CTR); + + flash = jedec_match_device(info->chip[0].id); + if (IS_ERR(flash)) { + dev_err(info->dev, "Unknown flash id (%08x)\n", + info->chip[0].id); + return -1; + } + + info->chip[0].size = flash->size_kb * 1024; + info->chip[0].addr_phys = phys_addr - info->chip[0].size + 1; + if (info->chip[0].addr_phys & 0xffff) { + dev_err(info->dev, "Chip 0 start address must align to 64KB\n"); + return -1; + } + info->sf_base_phys = info->chip[0].addr_phys; + info->sf_total_size = info->chip[0].size; + pr_info("SFC: Chip 0 @ %08x (size: %d)\n", info->chip[0].addr_phys, + info->chip[0].size); + + sf_calc_ccr(&info->chip[0]); + writel(info->chip[0].ccr, info->base + SF_CHIP_SEL_0_CFG); + + if (info->chip[1].id != FLASH_UNKNOWN) { + flash = jedec_match_device(info->chip[1].id); + if (IS_ERR(flash)) { + dev_err(info->dev, "Unknown flash id (%08x)\n", + info->chip[1].id); + return -1; + } + + info->chip[0].size = flash->size_kb * 1024; + info->chip[1].addr_phys = info->chip[0].addr_phys - + info->chip[1].size; + if (info->chip[1].addr_phys & 0xffff) { + dev_err(info->dev, "Chip 1 start address must align to 64KB\n"); + info->chip[1].id = FLASH_UNKNOWN; + return 0; + } + info->sf_base_phys = info->chip[1].addr_phys; + info->sf_total_size += info->chip[1].size; + pr_info("SFC: Chip 1 @ %08x (size: %d)\n", + info->chip[1].addr_phys, info->chip[1].size); + + sf_calc_ccr(&info->chip[1]); + writel(info->chip[1].ccr, info->base + SF_CHIP_SEL_1_CFG); + } + + return 0; +} + +static int sf_check_error(struct device *dev, u32 code) +{ + if (code & SF_ERR_TIMEOUT) { + dev_err(dev, "Serial flash timeout\n"); + return -ETIMEDOUT; + } + + if (code & SF_ERR_WR_PROT_ERR) { + dev_err(dev, "Serial flash write-protected\n"); + return -EIO; + } + + if (code & SF_ERR_MEM_REGION_ERR) { + dev_err(dev, "Serial flash memory region error\n"); + return -EIO; + } + + if (code & SF_ERR_PWR_DWN_ACC_ERR) { + dev_err(dev, "Serial flash power down access error\n"); + return -EIO; + } + + if (code & SF_ERR_PCMD_OP_ERR) { + dev_err(dev, "Serial flash program CMD OP error\n"); + return -EIO; + } + + if (code & SF_ERR_PCMD_ACC_ERR) { + dev_err(dev, "Serial flash program CMD OP access error\n"); + return -EIO; + } + + if (code & SF_ERR_MASLOCK_ERR) { + dev_err(dev, "Serial flash master lock error\n"); + return -EIO; + } + + /* no error */ + return 0; +} + +static int sf_spi_read_status(struct wmt_sf_data *info, int chip) +{ + u32 timeout = 0x30000000; + u32 temp; + int err; + + do { + if (chip == 0) + temp = readl_relaxed(info->base + SF_SPI_MEM_0_SR_ACC); + else + temp = readl_relaxed(info->base + SF_SPI_MEM_1_SR_ACC); + + if ((temp & 0x1) == 0x0) + break; + + err = sf_check_error(info->dev, + readl(info->base + SF_SPI_ERROR_STATUS)); + if (err) { + writel(0x3f, info->base + SF_SPI_ERROR_STATUS); + return err; + } + timeout--; + } while (timeout); + + if (timeout == 0) { + dev_err(info->dev, "spi request timed-out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int sf_sector_erase(struct wmt_sf_data *info, u32 addr) +{ + int chip; + u32 val; + + if ((info->sf_base_phys + addr) < info->chip[0].addr_phys) { + chip = 0; + writel(SF_CS0_WR_EN, info->base + SF_SPI_WR_EN_CTR); + } else { + chip = 1; + writel(SF_CS1_WR_EN, info->base + SF_SPI_WR_EN_CTR); + } + + addr &= ~(info->sf_mtd->erasesize - 1); + writel(addr, info->base + SF_SPI_ER_START_ADDR); + + writel(SF_SEC_ER_EN, info->base + SF_SPI_ER_CTR); + + val = sf_spi_read_status(info, chip); + + writel(0, info->base + SF_SPI_WR_EN_CTR); + return val; +} + +static int sf_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct wmt_sf_data *info = mtd->priv; + int ret; + + ret = clk_enable(info->sf_clk); + if (ret) + return ret; + + ret = sf_sector_erase(info, (u32)instr->addr); + if (ret) { + clk_disable(info->sf_clk); + return ret; + } + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + clk_disable(info->sf_clk); + return 0; +} + +static int sf_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct wmt_sf_data *info = mtd->priv; + int ret; + + ret = clk_enable(info->sf_clk); + if (ret) + return ret; + + if (sf_spi_read_status(info, 0)) + return -EBUSY; + if (sf_spi_read_status(info, 1)) + return -EBUSY; + + if (from + len > mtd->size) { + dev_err(info->dev, "Request out of bounds (from=%llu, len=%d)\n", + from, len); + return -EINVAL; + } + + memcpy_fromio(buf, info->sf_base_virt + from, len); + *retlen = len; + + clk_disable(info->sf_clk); + return 0; +} + +static int sf_sector_write(struct wmt_sf_data *info, loff_t to, size_t len, + const u_char *buf) +{ + int ret; + int data_size; + u32 count; + u32 addr_to = (u32)info->sf_base_virt + to; + + ret = clk_enable(info->sf_clk); + if (ret) + return ret; + + if (sf_spi_read_status(info, 0)) + return -EBUSY; + if (sf_spi_read_status(info, 1)) + return -EBUSY; + + writel(SF_CS0_WR_EN | SF_CS1_WR_EN, info->base + SF_SPI_WR_EN_CTR); + + count = 0; + while (len) { + data_size = (len >= 4) ? 4 : 1; + memcpy_toio(((void *)(addr_to + count)), buf + count, + data_size); + count += data_size; + len -= data_size; + + if (len) { + data_size = (len >= 4) ? 4 : 1; + memcpy_toio(((void *)(addr_to + count)), buf + count, + data_size); + count += data_size; + len -= data_size; + } + + ret = sf_spi_read_status(info, 0); + if (ret) { + clk_disable(info->sf_clk); + return ret; + } + } + + writel(0, info->base + SF_SPI_WR_EN_CTR); + + clk_disable(info->sf_clk); + + return count; +} + +static int sf_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct wmt_sf_data *info = mtd->priv; + + *retlen = sf_sector_write(info, to, len, buf); + + return 0; +} + +static int mtdsf_init_device(struct device *dev, struct mtd_info *mtd, + unsigned long size, char *name) +{ + mtd->name = name; + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->size = size; + mtd->erasesize = WMT_ERASESIZE; + mtd->owner = THIS_MODULE; + mtd->_erase = sf_erase; + mtd->_read = sf_read; + mtd->_write = sf_write; + mtd->writesize = 1; + + if (mtd_device_register(mtd, NULL, 0)) { + dev_err(dev, "Erroring adding MTD device\n"); + return -EIO; + } + + return 0; +} + +static int wmt_sf_probe(struct platform_device *pdev) +{ + struct wmt_sf_data *info; + struct device_node *np = pdev->dev.of_node; + int err; + + if (!np) { + dev_err(&pdev->dev, "Invalid devicetree node\n"); + return -EINVAL; + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "Failed to get memory for SF info\n"); + return -ENOMEM; + } + + info->dev = &pdev->dev; + + info->base = of_iomap(np, 0); + if (!info->base) { + dev_err(&pdev->dev, "Failed to map register memory\n"); + return -ENOMEM; + } + + info->sf_clk = of_clk_get(np, 0); + if (!info->sf_clk) { + dev_err(&pdev->dev, "Failed to get clock from device tree\n"); + return -EINVAL; + } + + err = clk_prepare_enable(info->sf_clk); + if (err) + return err; + + err = wmt_sf_init_hw(info); + clk_disable(info->sf_clk); + if (err) { + dev_err(&pdev->dev, "Failed to initialize SF hardware\n"); + return -EIO; + } + + info->sf_base_virt = devm_ioremap(&pdev->dev, info->sf_base_phys, + info->sf_total_size); + if (!info->sf_base_virt) { + dev_err(&pdev->dev, "Failed to map serial flash memory\n"); + return -ENOMEM; + } + + info->sf_mtd = devm_kzalloc(&pdev->dev, sizeof(struct mtd_info), + GFP_KERNEL); + if (!info->sf_mtd) { + dev_err(&pdev->dev, "Failed to allocate SFMTD memory\n"); + return -ENOMEM; + } + + err = mtdsf_init_device(info->dev, info->sf_mtd, info->sf_total_size, + "Wondermedia SF Device"); + if (err) + return err; + + info->sf_mtd->priv = info; + dev_set_drvdata(&pdev->dev, info); + + pr_info("Wondermedia Serial Flash Controller initialized\n"); + + return 0; +} + +static int wmt_sf_remove(struct platform_device *pdev) +{ + struct wmt_sf_data *info = dev_get_drvdata(&pdev->dev); + + mtd_device_unregister(info->sf_mtd); + + return 0; +} + +static const struct of_device_id wmt_dt_ids[] = { + { .compatible = "wm,wm8505-sf", }, + {} +}; + +static struct platform_driver wmt_sf_driver = { + .probe = wmt_sf_probe, + .remove = wmt_sf_remove, + .driver = { + .name = "wmt-sf", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(wmt_dt_ids), + }, +}; + +module_platform_driver(wmt_sf_driver); + +MODULE_AUTHOR("Tony Prisk "); +MODULE_DESCRIPTION("Wondermedia SoC Serial Flash driver"); +MODULE_LICENSE("GPL v2");