From: Tony Prisk <linux@prisktech.co.nz>
To: David Woodhouse <dwmw2@infradead.org>
Cc: vt8500-wm8505-linux-kernel@googlegroups.com,
Tony Prisk <linux@prisktech.co.nz>,
linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2] mtd: vt8500: Add support for Wondermedia Serial Flash Controller
Date: Mon, 31 Dec 2012 10:00:07 +1300 [thread overview]
Message-ID: <1356901207-13536-1-git-send-email-linux@prisktech.co.nz> (raw)
This patch adds support for the Wondermedia serial flash controller
found on WM8505, WM8650 and WM8850 SoCs.
Signed-off-by: Tony Prisk <linux@prisktech.co.nz>
---
v2: Whitespace tidyup
drivers/mtd/devices/Kconfig | 7 +
drivers/mtd/devices/Makefile | 3 +-
drivers/mtd/devices/wmt_sflash.c | 614 ++++++++++++++++++++++++++++++++++++++
3 files changed, 623 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..6c4bbd4 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -128,6 +128,13 @@ 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 MTD
+ help
+ Enable this option to provide support for the Wondermedia SoC serial
+ flash controller.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 395733a..10b8bec 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
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..49359ea
--- /dev/null
+++ b/drivers/mtd/devices/wmt_sflash.c
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * 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 <linux/io.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+#include <linux/mtd/mtd.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_flash_id {
+ u32 id;
+ u32 size; /* Size in KB */
+};
+
+static struct wmt_flash_id flash_ids[] = {
+ { SF_ID(MFR_SPANSION, SPAN_FL016A), 2048 },
+ { SF_ID(MFR_SPANSION, SPAN_FL064A), 8192 },
+ { SF_ID(MFR_EON, EON_25P16), 2048 },
+ { SF_ID(MFR_EON, EON_25P64), 8192 },
+ { SF_ID(MFR_EON, EON_25F40), 512 },
+ { SF_ID(MFR_EON, EON_25F16), 2048 },
+ { SF_ID(MFR_ATMEL, AT_25DF041A), 512 },
+ { SF_ID(MFR_NUMONYX, NX_25P16), 2048 },
+ { SF_ID(MFR_NUMONYX, NX_25P64), 8192 },
+ { SF_ID(MFR_FUDAN, FM_25F04), 512 },
+ { SF_ID(MFR_SST, SST_VF016B), 2048 },
+ { SF_ID(MFR_MXIC, MX_L512), 64 },
+ { SF_ID(MFR_MXIC, MX_L4005A), 512 },
+ { SF_ID(MFR_MXIC, MX_L1605D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3205D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L6405D), 8192 },
+ { SF_ID(MFR_MXIC, MX_L1635D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3235D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L12805D), 16384 },
+ { SF_ID(MFR_WINBOND, WB_W25X40BV), 512 },
+ { SF_ID(MFR_WINBOND, WB_X16A), 2048 },
+ { SF_ID(MFR_WINBOND, WB_X32), 4096 },
+ { SF_ID(MFR_WINBOND, WB_X64), 8192 },
+ { 0, 0 },
+};
+
+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 u32 sf_get_chip_size(struct device *dev, u32 id)
+{
+ int i;
+ for (i = 0; flash_ids[i].id != 0; i++)
+ if (flash_ids[i].id == id)
+ return flash_ids[i].size * 1024;
+
+ dev_err(dev, "Unknown flash id (%08x)\n", id);
+ return 0;
+}
+
+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;
+ 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);
+
+ info->chip[0].size = sf_get_chip_size(info->dev, info->chip[0].id);
+ if (info->chip[0].size == 0)
+ return -1;
+
+ 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) {
+ info->chip[1].size = sf_get_chip_size(info->dev,
+ info->chip[1].id);
+ 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(((u_char *)(addr_to + count)), buf + count,
+ data_size);
+ count += data_size;
+ len -= data_size;
+
+ if (len) {
+ data_size = (len >= 4) ? 4 : 1;
+ memcpy_toio(((u_char *)(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 <linux@prisktech.co.nz>");
+MODULE_DESCRIPTION("Wondermedia SoC Serial Flash driver");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
WARNING: multiple messages have this Message-ID (diff)
From: linux@prisktech.co.nz (Tony Prisk)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2] mtd: vt8500: Add support for Wondermedia Serial Flash Controller
Date: Mon, 31 Dec 2012 10:00:07 +1300 [thread overview]
Message-ID: <1356901207-13536-1-git-send-email-linux@prisktech.co.nz> (raw)
This patch adds support for the Wondermedia serial flash controller
found on WM8505, WM8650 and WM8850 SoCs.
Signed-off-by: Tony Prisk <linux@prisktech.co.nz>
---
v2: Whitespace tidyup
drivers/mtd/devices/Kconfig | 7 +
drivers/mtd/devices/Makefile | 3 +-
drivers/mtd/devices/wmt_sflash.c | 614 ++++++++++++++++++++++++++++++++++++++
3 files changed, 623 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..6c4bbd4 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -128,6 +128,13 @@ 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 MTD
+ help
+ Enable this option to provide support for the Wondermedia SoC serial
+ flash controller.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 395733a..10b8bec 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
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..49359ea
--- /dev/null
+++ b/drivers/mtd/devices/wmt_sflash.c
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * 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 <linux/io.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+#include <linux/mtd/mtd.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_flash_id {
+ u32 id;
+ u32 size; /* Size in KB */
+};
+
+static struct wmt_flash_id flash_ids[] = {
+ { SF_ID(MFR_SPANSION, SPAN_FL016A), 2048 },
+ { SF_ID(MFR_SPANSION, SPAN_FL064A), 8192 },
+ { SF_ID(MFR_EON, EON_25P16), 2048 },
+ { SF_ID(MFR_EON, EON_25P64), 8192 },
+ { SF_ID(MFR_EON, EON_25F40), 512 },
+ { SF_ID(MFR_EON, EON_25F16), 2048 },
+ { SF_ID(MFR_ATMEL, AT_25DF041A), 512 },
+ { SF_ID(MFR_NUMONYX, NX_25P16), 2048 },
+ { SF_ID(MFR_NUMONYX, NX_25P64), 8192 },
+ { SF_ID(MFR_FUDAN, FM_25F04), 512 },
+ { SF_ID(MFR_SST, SST_VF016B), 2048 },
+ { SF_ID(MFR_MXIC, MX_L512), 64 },
+ { SF_ID(MFR_MXIC, MX_L4005A), 512 },
+ { SF_ID(MFR_MXIC, MX_L1605D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3205D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L6405D), 8192 },
+ { SF_ID(MFR_MXIC, MX_L1635D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3235D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L12805D), 16384 },
+ { SF_ID(MFR_WINBOND, WB_W25X40BV), 512 },
+ { SF_ID(MFR_WINBOND, WB_X16A), 2048 },
+ { SF_ID(MFR_WINBOND, WB_X32), 4096 },
+ { SF_ID(MFR_WINBOND, WB_X64), 8192 },
+ { 0, 0 },
+};
+
+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 u32 sf_get_chip_size(struct device *dev, u32 id)
+{
+ int i;
+ for (i = 0; flash_ids[i].id != 0; i++)
+ if (flash_ids[i].id == id)
+ return flash_ids[i].size * 1024;
+
+ dev_err(dev, "Unknown flash id (%08x)\n", id);
+ return 0;
+}
+
+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;
+ 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);
+
+ info->chip[0].size = sf_get_chip_size(info->dev, info->chip[0].id);
+ if (info->chip[0].size == 0)
+ return -1;
+
+ 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) {
+ info->chip[1].size = sf_get_chip_size(info->dev,
+ info->chip[1].id);
+ 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(((u_char *)(addr_to + count)), buf + count,
+ data_size);
+ count += data_size;
+ len -= data_size;
+
+ if (len) {
+ data_size = (len >= 4) ? 4 : 1;
+ memcpy_toio(((u_char *)(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 <linux@prisktech.co.nz>");
+MODULE_DESCRIPTION("Wondermedia SoC Serial Flash driver");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
WARNING: multiple messages have this Message-ID (diff)
From: Tony Prisk <linux@prisktech.co.nz>
To: David Woodhouse <dwmw2@infradead.org>
Cc: linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
vt8500-wm8505-linux-kernel@googlegroups.com,
linux-mtd@lists.infradead.org, Tony Prisk <linux@prisktech.co.nz>
Subject: [PATCH v2] mtd: vt8500: Add support for Wondermedia Serial Flash Controller
Date: Mon, 31 Dec 2012 10:00:07 +1300 [thread overview]
Message-ID: <1356901207-13536-1-git-send-email-linux@prisktech.co.nz> (raw)
This patch adds support for the Wondermedia serial flash controller
found on WM8505, WM8650 and WM8850 SoCs.
Signed-off-by: Tony Prisk <linux@prisktech.co.nz>
---
v2: Whitespace tidyup
drivers/mtd/devices/Kconfig | 7 +
drivers/mtd/devices/Makefile | 3 +-
drivers/mtd/devices/wmt_sflash.c | 614 ++++++++++++++++++++++++++++++++++++++
3 files changed, 623 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..6c4bbd4 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -128,6 +128,13 @@ 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 MTD
+ help
+ Enable this option to provide support for the Wondermedia SoC serial
+ flash controller.
+
config MTD_SLRAM
tristate "Uncached system RAM"
help
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 395733a..10b8bec 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
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..49359ea
--- /dev/null
+++ b/drivers/mtd/devices/wmt_sflash.c
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * 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 <linux/io.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+#include <linux/mtd/mtd.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_flash_id {
+ u32 id;
+ u32 size; /* Size in KB */
+};
+
+static struct wmt_flash_id flash_ids[] = {
+ { SF_ID(MFR_SPANSION, SPAN_FL016A), 2048 },
+ { SF_ID(MFR_SPANSION, SPAN_FL064A), 8192 },
+ { SF_ID(MFR_EON, EON_25P16), 2048 },
+ { SF_ID(MFR_EON, EON_25P64), 8192 },
+ { SF_ID(MFR_EON, EON_25F40), 512 },
+ { SF_ID(MFR_EON, EON_25F16), 2048 },
+ { SF_ID(MFR_ATMEL, AT_25DF041A), 512 },
+ { SF_ID(MFR_NUMONYX, NX_25P16), 2048 },
+ { SF_ID(MFR_NUMONYX, NX_25P64), 8192 },
+ { SF_ID(MFR_FUDAN, FM_25F04), 512 },
+ { SF_ID(MFR_SST, SST_VF016B), 2048 },
+ { SF_ID(MFR_MXIC, MX_L512), 64 },
+ { SF_ID(MFR_MXIC, MX_L4005A), 512 },
+ { SF_ID(MFR_MXIC, MX_L1605D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3205D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L6405D), 8192 },
+ { SF_ID(MFR_MXIC, MX_L1635D), 2048 },
+ { SF_ID(MFR_MXIC, MX_L3235D), 4192 },
+ { SF_ID(MFR_MXIC, MX_L12805D), 16384 },
+ { SF_ID(MFR_WINBOND, WB_W25X40BV), 512 },
+ { SF_ID(MFR_WINBOND, WB_X16A), 2048 },
+ { SF_ID(MFR_WINBOND, WB_X32), 4096 },
+ { SF_ID(MFR_WINBOND, WB_X64), 8192 },
+ { 0, 0 },
+};
+
+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 u32 sf_get_chip_size(struct device *dev, u32 id)
+{
+ int i;
+ for (i = 0; flash_ids[i].id != 0; i++)
+ if (flash_ids[i].id == id)
+ return flash_ids[i].size * 1024;
+
+ dev_err(dev, "Unknown flash id (%08x)\n", id);
+ return 0;
+}
+
+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;
+ 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);
+
+ info->chip[0].size = sf_get_chip_size(info->dev, info->chip[0].id);
+ if (info->chip[0].size == 0)
+ return -1;
+
+ 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) {
+ info->chip[1].size = sf_get_chip_size(info->dev,
+ info->chip[1].id);
+ 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(((u_char *)(addr_to + count)), buf + count,
+ data_size);
+ count += data_size;
+ len -= data_size;
+
+ if (len) {
+ data_size = (len >= 4) ? 4 : 1;
+ memcpy_toio(((u_char *)(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 <linux@prisktech.co.nz>");
+MODULE_DESCRIPTION("Wondermedia SoC Serial Flash driver");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
next reply other threads:[~2012-12-30 21:00 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-12-30 21:00 Tony Prisk [this message]
2012-12-30 21:00 ` [PATCH v2] mtd: vt8500: Add support for Wondermedia Serial Flash Controller Tony Prisk
2012-12-30 21:00 ` Tony Prisk
2013-01-15 14:55 ` Artem Bityutskiy
2013-01-15 14:55 ` Artem Bityutskiy
2013-01-15 14:55 ` Artem Bityutskiy
2013-01-15 17:45 ` Tony Prisk
2013-01-15 17:45 ` Tony Prisk
2013-01-15 17:45 ` Tony Prisk
2013-01-15 19:11 ` Artem Bityutskiy
2013-01-15 19:11 ` Artem Bityutskiy
2013-01-15 19:11 ` Artem Bityutskiy
2013-01-15 23:30 ` Tony Prisk
2013-01-15 23:30 ` Tony Prisk
2013-01-15 23:30 ` Tony Prisk
-- strict thread matches above, loose matches on Subject: below --
2013-01-15 17:53 Tony Prisk
2013-01-15 17:53 ` Tony Prisk
2013-01-15 17:53 ` Tony Prisk
2013-01-15 19:36 ` Artem Bityutskiy
2013-01-15 19:36 ` Artem Bityutskiy
2013-01-15 19:36 ` Artem Bityutskiy
2013-01-15 23:52 ` Tony Prisk
2013-01-15 23:52 ` Tony Prisk
2013-01-15 23:52 ` Tony Prisk
2013-01-17 11:21 ` Artem Bityutskiy
2013-01-17 11:21 ` Artem Bityutskiy
2013-01-17 11:21 ` Artem Bityutskiy
2013-01-17 12:30 ` Artem Bityutskiy
2013-01-17 12:30 ` Artem Bityutskiy
2013-01-17 12:30 ` Artem Bityutskiy
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1356901207-13536-1-git-send-email-linux@prisktech.co.nz \
--to=linux@prisktech.co.nz \
--cc=dwmw2@infradead.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mtd@lists.infradead.org \
--cc=vt8500-wm8505-linux-kernel@googlegroups.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.