mtd: spifc: add spifc support for txl
authorYi Zeng <yi.zeng@amlogic.com>
Thu, 2 Aug 2018 06:22:04 +0000 (14:22 +0800)
committerYixun Lan <yixun.lan@amlogic.com>
Tue, 7 Aug 2018 05:47:48 +0000 (22:47 -0700)
PD#171041: mtd: spifc: add spifc support for txl

Change-Id: I487161c6e85e3b232ed0c3891784b5a37f6d878c
Signed-off-by: Yi Zeng <yi.zeng@amlogic.com>
Documentation/devicetree/bindings/mtd/aml-spifc.txt [new file with mode: 0644]
MAINTAINERS
arch/arm64/boot/dts/amlogic/mesontxl.dtsi
arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
drivers/amlogic/Kconfig
drivers/amlogic/Makefile
drivers/amlogic/spi-nor/Kconfig [new file with mode: 0644]
drivers/amlogic/spi-nor/Makefile [new file with mode: 0644]
drivers/amlogic/spi-nor/aml-spifc.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/mtd/aml-spifc.txt b/Documentation/devicetree/bindings/mtd/aml-spifc.txt
new file mode 100644 (file)
index 0000000..8c11cf3
--- /dev/null
@@ -0,0 +1,27 @@
+Amlogic SPI Flash Controller
+
+Required properties:
+- compatible : Should be "amlogic,aml-spi-nor"
+- reg : Offset and range of the register set for the controller device, different value for different IC.
+- clocks : Handle to spi-nor flash controller clock, please refer to IC spec.
+- read-capability : read capability of this flash, please refer to flash spec. 0:normal,1:fast,2:dual read,3:quad read.
+- spifc-io-width : the io number of spifc on the board.
+- cs_gpios : we use cs pin as gpio.
+
+Example:
+spifc: spifc@c1108c80 {
+       status = "disabled";
+       compatible = "amlogic,aml-spi-nor";
+       reg = <0x0 0xc1108c80 0x0 0x80>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&spifc_all_pins>;
+       clocks = <&clkc CLKID_CLK81>;
+       clock-names = "core";
+       spi-nor@0 {
+               compatible = "jedec,spi-nor";
+               spifc-frequency = <40000000>;
+               read-capability = <2>;
+               spifc-io-width = <2>;
+               cs_gpios = <&gpio BOOT_11 GPIO_ACTIVE_HIGH>;
+       };
+};
\ No newline at end of file
index dd498851d8d3d427a38787a7a448cb6f647a0439..388d95d6e0b2c8f221386804261b89dfad2a07c0 100644 (file)
@@ -13677,6 +13677,13 @@ F:     drivers/amlogic/mtd/
 M:     Yonghui Yu <yonghui.yu@amlogic.com>
 F:     drivers/amlogic/mtd/boot.c
 
+AMLOGIC SPIFC DRIVER
+M:     Yi Zeng <yi.zeng@amlogic.com>
+F:     driver/amlogic/spi-nor/
+F:     driver/amlogic/spi-nor/aml-spifc.c
+F:     driver/amlogic/spi-nor/Kconfig
+F:     driver/amlogic/spi-nor/Makefile
+
 AMLOGIC GPU DEVICETREE
 M:     Jiyu Yang <jiyu.yang@amlogic.com>
 F:     arch/arm64/boot/dts/amlogic/mesongxtvbb-gpu-t83x.dtsi
index dba40c96293487e894e54a0c07dc838dc7612f55..cf657a9ca28b043f8dd50c3dc8065565371066c9 100644 (file)
                };
        };
 
+       spifc_cs_pin:spifc_cs_pin {
+               mux {
+                       groups = "nor_cs";
+                       function = "nor";
+                       bias-pull-up;
+               };
+       };
+
+       spifc_pulldown: spifc_pulldown {
+               mux {
+                       groups = "nor_d",
+                               "nor_q",
+                               "nor_c";
+                       function = "nor";
+                       bias-pull-down;
+               };
+       };
+
+       spifc_pullup: spifc_pullup {
+               mux {
+                       groups = "nor_cs";
+                       function = "nor";
+                       bias-pull-up;
+               };
+       };
+
+       spifc_all_pins: spifc_all_pins {
+               mux {
+                       groups =  "nor_d",
+                               "nor_q",
+                               "nor_c";
+                       function = "nor";
+                       input-enable;
+                       bias-pull-down;
+               };
+       };
+
        sd_clk_cmd_pins:sd_clk_cmd_pins{
                mux {
                        groups = "sdcard_cmd",
index c4db667ca903c63dd430e791162364a4146061ef..447176e5f8dfa423845c8a0957d33b9d73093288 100644 (file)
                };
        };
 
+       spifc: spifc@c1108c80 {
+               status = "disabled";
+               compatible = "amlogic,aml-spi-nor";
+               reg = <0x0 0xc1108c80 0x0 0x80>;
+               pinctrl-names = "default";
+               pinctrl-0 = <&spifc_all_pins>;
+               clocks = <&clkc CLKID_CLK81>;
+               clock-names = "core";
+               spi-nor@0 {
+                       compatible = "jedec,spi-nor";
+                       spifc-frequency = <40000000>;
+                       read-capability = <2>;/* dual read 1_1_2 */
+                       spifc-io-width = <2>;/* txl only support 2 io */
+                       cs_gpios = <&gpio BOOT_11 GPIO_ACTIVE_HIGH>;
+               };
+       };
+
        unifykey {
                compatible = "amlogic, unifykey";
                status = "okay";
index ea7a11b26acc8bd82f5b282a5fd393071ee90d78..d1a1afbdea25a4c8ca7c47108ecbbbd3c4b3e7fc 100644 (file)
@@ -133,5 +133,7 @@ source "drivers/amlogic/debug/Kconfig"
 source "drivers/amlogic/defendkey/Kconfig"
 
 source "drivers/amlogic/battery/Kconfig"
+
+source "drivers/amlogic/spi-nor/Kconfig"
 endmenu
 endif
index e28869c9eb9732f49b99b08ec676b1078aaefb1f..801a195ac1c34cc4de264918de9acfd0ad873dbc 100644 (file)
@@ -125,3 +125,5 @@ obj-$(CONFIG_AMLOGIC_ATV_DEMOD) += atv_demod/
 obj-$(CONFIG_AMLOGIC_DEBUG) += debug/
 
 obj-$(CONFIG_AMLOGIC_DEFENDKEY) += defendkey/
+
+obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/
\ No newline at end of file
diff --git a/drivers/amlogic/spi-nor/Kconfig b/drivers/amlogic/spi-nor/Kconfig
new file mode 100644 (file)
index 0000000..f5f61a3
--- /dev/null
@@ -0,0 +1,16 @@
+#
+# Amlogic SPI NOR Flash device configuration
+#
+
+menu "Meson SPI NOR Flash Support"
+
+config SPI_AML_SPIFC
+       bool "Meson SPI NOR Flash device"
+       default n
+       depends on MTD_SPI_NOR
+       help
+               Amlogic spi nor flash support config.
+               Support for spi nor flash on Amlogic Meson platforms
+               Need open in defconfig, set CONFIG_SPI_AML_SPIFC=y
+               need check
+endmenu
diff --git a/drivers/amlogic/spi-nor/Makefile b/drivers/amlogic/spi-nor/Makefile
new file mode 100644 (file)
index 0000000..f753cc8
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# AMLOGIC SPI Flash driver for mtd subsystem
+#
+
+obj-$(CONFIG_SPI_AML_SPIFC) += aml-spifc.o
diff --git a/drivers/amlogic/spi-nor/aml-spifc.c b/drivers/amlogic/spi-nor/aml-spifc.c
new file mode 100644 (file)
index 0000000..977ab64
--- /dev/null
@@ -0,0 +1,629 @@
+/*
+ * drivers/amlogic/spi-nor/aml-spifc.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/bitops.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define AML_SPIFC_CMD          0x00
+#define AML_SPIFC_ADDR         0x04
+#define AML_SPIFC_CTRL         0x08
+#define AML_SPIFC_CTRL1                0x0c
+#define AML_SPIFC_STATUS       0x10
+#define AML_SPIFC_CTRL2                0x14
+#define AML_SPIFC_CLK          0x18
+#define AML_SPIFC_USER         0x1c
+#define AML_SPIFC_USER1                0x20
+#define AML_SPIFC_USER2                0x24
+#define AML_SPIFC_USER3                0x28
+#define AML_SPIFC_PIN          0x2c
+#define AML_SPIFC_SLAVE                0x30
+#define AML_SPIFC_SLAVE1       0x34
+#define AML_SPIFC_SLAVE2       0x38
+#define AML_SPIFC_SLAVE3       0x3c
+#define AML_SPIFC_CACHE0       0x40
+#define AML_SPIFC_CACHE1       0x44
+#define AML_SPIFC_CACHE2       0x48
+#define AML_SPIFC_CACHE3       0x4c
+#define AML_SPIFC_CACHE4       0x50
+#define AML_SPIFC_CACHE5       0x54
+#define AML_SPIFC_CACHE6       0x58
+#define AML_SPIFC_CACHE7       0x5c
+#define AML_SPIFC_BUF0         0x60
+#define AML_SPIFC_BUF1         0x64
+#define AML_SPIFC_BUF2         0x68
+#define AML_SPIFC_BUF3         0x6c
+#define AML_SPIFC_BUF4         0x70
+#define AML_SPIFC_BUF5         0x74
+#define AML_SPIFC_BUF6         0x78
+#define AML_SPIFC_BUF7         0x7c
+
+/* command register config bit */
+#define CMD_READ_FINISH                BIT(31)
+#define CMD_USER_DEFINE                BIT(18)
+#define CMD_ADDR_OUT           BIT(15)
+#define CMD_DATA_IN                    BIT(13)
+#define CMD_DATA_OUT           BIT(12)
+
+/* control register config bit */
+#define CTRL_READ_QIO          BIT(24)
+#define CTRL_READ_DIO          BIT(23)
+#define CTRL_WP_ENABLE         BIT(21)
+#define CTRL_READ_QOUT         BIT(20)
+#define CTRL_AHB_ENABLE                BIT(17)
+#define CTRL_READ_DOUT         BIT(14)
+#define CTRL_READ_FAST         BIT(13)
+
+/* user register config bit */
+#define USER_CMD_DEFINE                BIT(31)
+#define USER_ADDR_OUT          BIT(30)
+#define USER_DUMMY_OUT         BIT(29)
+#define USER_DATA_IN           BIT(28)
+#define USER_DATA_OUT          BIT(27)
+#define USER_WRITE_1WIRE       BIT(16)
+#define USER_WRITE_QIO         BIT(15)
+#define USER_WRITE_DIO         BIT(14)
+#define USER_WRITE_QOUT                BIT(13)
+#define USER_WRITE_DOUT                BIT(12)
+#define USER_CS_SETUP          BIT(5)
+#define USER_CS_HOLD           BIT(4)
+#define USER_AHB_CONFIG                BIT(3)
+#define USER_COMPATIBLE                BIT(2)
+#define USER_ADDR_WIDTH                BIT(1)
+
+/* user register1  */
+#define USER1_ADDR_SHIFTS      26
+#define USER1_DOUT_SHIFTS      17
+#define USER1_DIN_SHIFTS       8
+
+/* clock register */
+#define CLK_EQUAL_SYS          BIT(31)
+#define CLK_PRESCALE_CNT       18
+#define CLK_DIV_CNT                    12
+#define CLK_HDIV_CNT           6
+#define CLK_LDIV_CNT           0
+
+/* user register2 config bit */
+#define USER2_CMD_BIT          7
+#define USER2_CMD_SHIFTS       28
+
+/* slave register config bit */
+#define SLAVE_SW_RESET         BIT(31)
+#define SLAVE_DEV_MODE         BIT(30)
+#define SLAVE_XFER_DONE                BIT(4)
+
+#define AML_SPIFC_CACHE_SIZE   64
+
+#define AML_SPIFC_OP_READ      BIT(0)
+#define AML_SPIFC_OP_WRITE     BIT(1)
+
+/* please note that "val" can not be 0 */
+#define aml_mask(val)  GENMASK((fls(val) - 1), (ffs(val) - 1))
+/* set function set bit to 1, clear function set bit to 0 */
+#define amlsf_set_bits(val, reg, mask) \
+       writel((readl(reg) | ((val) & (mask))), (reg))
+#define amlsf_clr_bits(val, reg, mask) \
+       writel((readl(reg) & (~((val) & (mask)))), (reg))
+#define amlsf_set_1bit(val, reg)       writel((readl(reg) | (val)), (reg))
+#define amlsf_clr_1bit(val, reg)       writel((readl(reg) & (~(val))), (reg))
+#define amlsf_clr_reg(reg)                     writel(0UL, (reg))
+/* #define AML_SPIFC_AHB       1 */
+#define AML_SPIFC_MAX_CLK_RATE 166666666
+
+struct aml_spi_flash {
+       struct device *dev;
+       struct mutex lock;
+
+       void __iomem *regbase;
+#ifdef AML_SPIFC_AHB
+       void __iomem *ahbbase;
+#endif
+       struct clk *clk;
+       unsigned int clkrate;
+       void *priv;
+       unsigned int cs;
+
+       struct spi_nor  *nor;
+};
+
+static void aml_spifc_get(struct aml_spi_flash *spi_flash)
+{
+       gpio_set_value(spi_flash->cs, 0);
+}
+
+static void aml_spifc_relax(struct aml_spi_flash *spi_flash)
+{
+       gpio_set_value(spi_flash->cs, 1);
+}
+
+static void aml_spifc_reset_read_mode(struct aml_spi_flash *spi_flash)
+{
+       unsigned int ctrl;
+
+       ctrl = CTRL_READ_QIO |
+                       CTRL_READ_DIO |
+                       CTRL_READ_QOUT |
+                       CTRL_READ_DOUT |
+                       CTRL_READ_FAST;
+       amlsf_clr_bits(ctrl, spi_flash->regbase +
+                                  AML_SPIFC_CTRL, aml_mask(ctrl));
+}
+
+static void aml_spifc_set_read_mode(struct aml_spi_flash *spi_flash)
+{
+       struct spi_nor *nor = spi_flash->nor;
+
+       aml_spifc_reset_read_mode(spi_flash);
+
+       switch (nor->flash_read) {
+       case SPI_NOR_FAST:
+               amlsf_set_1bit(CTRL_READ_FAST, spi_flash->regbase +
+                                          AML_SPIFC_CTRL);
+               break;
+       case SPI_NOR_DUAL:
+               amlsf_set_1bit(CTRL_READ_DOUT, spi_flash->regbase +
+                                          AML_SPIFC_CTRL);
+               break;
+       case SPI_NOR_QUAD:
+               amlsf_set_1bit(CTRL_READ_QOUT, spi_flash->regbase +
+                                          AML_SPIFC_CTRL);
+               break;
+       case SPI_NOR_NORMAL:
+       default:
+               break;
+       }
+}
+
+static unsigned int aml_spifc_set_write_mode(struct aml_spi_flash *spi_flash)
+{
+       unsigned int user = 0UL;
+       /*
+        * TODO:
+        * set write mode by program_proto in next version
+        * the kernel-4.9 do not support D/Q write yet
+        */
+       return user;
+}
+
+static int aml_spifc_transfer(struct aml_spi_flash *spi_flash,
+u_char *buf, size_t length, u8 op_flag)
+{
+       unsigned int user1;
+       size_t len, count;
+       unsigned int *ptr;
+       int i;
+       u8 temp_buf[AML_SPIFC_CACHE_SIZE];
+
+       if (length > AML_SPIFC_CACHE_SIZE)
+               return -ENOBUFS;
+
+       len = length;
+       count = (len / 4) + !!(len % 4);
+       if (op_flag == AML_SPIFC_OP_READ) {
+               ptr = (u32 *)temp_buf;
+               writel(USER_DATA_IN, spi_flash->regbase +
+                          AML_SPIFC_USER);
+               user1 = ((len << 3) - 1) << USER1_DIN_SHIFTS;
+               writel(user1, spi_flash->regbase +
+                          AML_SPIFC_USER1);
+               amlsf_clr_reg(spi_flash->regbase +
+                                         AML_SPIFC_USER2);
+               amlsf_clr_reg(spi_flash->regbase +
+                                         AML_SPIFC_ADDR);
+               writel(CMD_USER_DEFINE, spi_flash->regbase +
+                          AML_SPIFC_CMD);
+               while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                          CMD_USER_DEFINE)
+               ;
+               for (i = 0; i < count; i++)
+                       *ptr++ = readl(spi_flash->regbase +
+                                                  AML_SPIFC_CACHE0 + 4 * i);
+               memcpy(buf, temp_buf, len);
+       } else if (op_flag == AML_SPIFC_OP_WRITE) {
+               ptr = (u32 *)buf;
+               for (i = 0; i < count; i++)
+                       writel(*ptr++, spi_flash->regbase +
+                                  AML_SPIFC_CACHE0 + 4 * i);
+               writel(USER_DATA_OUT, spi_flash->regbase +
+                          AML_SPIFC_USER);
+               user1 = ((len << 3) - 1) << USER1_DOUT_SHIFTS;
+               writel(user1, spi_flash->regbase +
+                          AML_SPIFC_USER1);
+               amlsf_clr_reg(spi_flash->regbase +
+                                         AML_SPIFC_USER2);
+               amlsf_clr_reg(spi_flash->regbase +
+                                         AML_SPIFC_ADDR);
+               writel(CMD_USER_DEFINE, spi_flash->regbase +
+                          AML_SPIFC_CMD);
+               while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                          CMD_USER_DEFINE)
+               ;
+       }
+       return 0;
+}
+
+static int aml_spifc_prep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+
+       mutex_lock(&spi_flash->lock);
+
+       return 0;
+}
+
+static void aml_spifc_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+
+       mutex_unlock(&spi_flash->lock);
+}
+
+static int aml_spifc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+       unsigned int user, user1, user2;
+       unsigned int *din;
+       int count, i;
+       u8 temp_buf[AML_SPIFC_CACHE_SIZE];
+
+       if (len > AML_SPIFC_CACHE_SIZE)
+               return -ENOBUFS;
+
+       aml_spifc_get(spi_flash);
+       aml_spifc_reset_read_mode(spi_flash);
+
+       din = (unsigned int *)temp_buf;
+       count = (len / 4) + !!(len % 4);
+       user = USER_CMD_DEFINE | USER_DATA_IN;
+       user1 = (((len << 3) - 1) & GENMASK(8, 0)) << USER1_DIN_SHIFTS;
+       user2 = (USER2_CMD_BIT << USER2_CMD_SHIFTS) | opcode;
+       writel(user, spi_flash->regbase +
+                  AML_SPIFC_USER);
+       writel(user1, spi_flash->regbase +
+                  AML_SPIFC_USER1);
+       writel(user2, spi_flash->regbase +
+                  AML_SPIFC_USER2);
+       writel(CMD_USER_DEFINE, spi_flash->regbase + AML_SPIFC_CMD);
+       while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                  CMD_USER_DEFINE)
+               ;
+       for (i = 0; i < count; i++)
+               *din++ = readl(spi_flash->regbase +
+                                          AML_SPIFC_CACHE0 + 4 * i);
+       memcpy(buf, temp_buf, len);
+       aml_spifc_relax(spi_flash);
+       return 0;
+}
+
+ssize_t aml_spifc_read(struct spi_nor *nor, loff_t from,
+size_t len, u_char *read_buf)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+       u_char *din;
+       unsigned int user, user1, user2;
+       size_t offset, trans;
+       loff_t addr;
+       int ret;
+
+       aml_spifc_get(spi_flash);
+       din = read_buf;
+       user = USER_CMD_DEFINE | USER_ADDR_OUT;
+       if (nor->addr_width == 4) {
+               addr = from & GENMASK(31, 0);
+               user1 = 31 << USER1_ADDR_SHIFTS;
+               user |= USER_ADDR_WIDTH;
+       } else {
+               addr = from & GENMASK(23, 0);
+               addr = addr << 8;
+               user1 = 23 << USER1_ADDR_SHIFTS;
+       }
+       aml_spifc_set_read_mode(spi_flash);
+       user2 = nor->read_opcode | USER2_CMD_BIT << USER2_CMD_SHIFTS;
+       writel(user, spi_flash->regbase +
+                  AML_SPIFC_USER);
+       writel(user1, spi_flash->regbase +
+                  AML_SPIFC_USER1);
+       writel(user2, spi_flash->regbase +
+                  AML_SPIFC_USER2);
+       writel(addr, spi_flash->regbase +
+                  AML_SPIFC_ADDR);
+       writel(CMD_USER_DEFINE, spi_flash->regbase +
+                  AML_SPIFC_CMD);
+       while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                  CMD_USER_DEFINE)
+               ;
+       for (offset = 0; offset < len; offset += AML_SPIFC_CACHE_SIZE) {
+               trans = min_t(size_t, (len - offset), AML_SPIFC_CACHE_SIZE);
+               ret = aml_spifc_transfer(spi_flash, din,
+                               trans, AML_SPIFC_OP_READ);
+               if (ret) {
+                       dev_warn(nor->dev, "spi-nor read error %s %d\n",
+                                        __func__, __LINE__);
+                       aml_spifc_relax(spi_flash);
+                       return ret;
+               }
+               din += trans;
+       }
+       aml_spifc_relax(spi_flash);
+       return len;
+}
+
+static int aml_spifc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+       unsigned int user, user1, user2;
+       unsigned int *dout;
+       int count, i;
+       u8 temp_buf[AML_SPIFC_CACHE_SIZE];
+
+       if (len > AML_SPIFC_CACHE_SIZE)
+               return -ENOBUFS;
+
+       aml_spifc_get(spi_flash);
+       dout = (unsigned int *)temp_buf;
+       user = USER_CMD_DEFINE;
+       if (len > 0) {
+               user |= USER_DATA_OUT;
+               user1 = (((len << 3) - 1) & GENMASK(8, 0)) << USER1_DOUT_SHIFTS;
+               memcpy(temp_buf, buf, len);
+               count = (len / 4) + !!(len % 4);
+               for (i = 0; i < count; i++)
+                       writel(*dout++, spi_flash->regbase +
+                                  AML_SPIFC_CACHE0 + 4 * i);
+       } else
+               user1 = 0;
+       user2 = (USER2_CMD_BIT << USER2_CMD_SHIFTS) | opcode;
+
+       writel(user, spi_flash->regbase +
+                  AML_SPIFC_USER);
+       writel(user1, spi_flash->regbase +
+                  AML_SPIFC_USER1);
+       writel(user2, spi_flash->regbase +
+                  AML_SPIFC_USER2);
+       writel(CMD_USER_DEFINE, spi_flash->regbase +
+                  AML_SPIFC_CMD);
+       while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                  CMD_USER_DEFINE)
+               ;
+       aml_spifc_relax(spi_flash);
+       return 0;
+}
+
+static ssize_t aml_spifc_write(struct spi_nor *nor, loff_t to,
+size_t len, const u_char *write_buf)
+{
+       struct aml_spi_flash *spi_flash = nor->priv;
+       u_char dout[AML_SPIFC_CACHE_SIZE] = {0xFF};
+       size_t offset, trans;
+       loff_t addr;
+       unsigned int user, user1, user2;
+       int ret;
+
+       addr = to;
+       aml_spifc_get(spi_flash);
+       user = aml_spifc_set_write_mode(spi_flash);
+       user |= USER_CMD_DEFINE | USER_ADDR_OUT;
+       if (nor->addr_width == 4) {
+               addr = to & GENMASK(31, 0);
+               user1 = 31 << USER1_ADDR_SHIFTS;
+               user |= USER_ADDR_WIDTH;
+       } else {
+               addr = to & GENMASK(23, 0);
+               /* controller request addr by this way */
+               addr = addr << 8;
+               user1 = 23 << USER1_ADDR_SHIFTS;
+       }
+       user2 = nor->program_opcode |
+                       USER2_CMD_BIT << USER2_CMD_SHIFTS;
+       writel(user, spi_flash->regbase +
+                  AML_SPIFC_USER);
+       writel(user1, spi_flash->regbase +
+                  AML_SPIFC_USER1);
+       writel(user2, spi_flash->regbase +
+                  AML_SPIFC_USER2);
+       writel(addr, spi_flash->regbase +
+                  AML_SPIFC_ADDR);
+       writel(CMD_USER_DEFINE, spi_flash->regbase +
+                  AML_SPIFC_CMD);
+       while (readl(spi_flash->regbase + AML_SPIFC_CMD) &
+                  CMD_USER_DEFINE)
+               ;
+
+       for (offset = 0; offset < len; offset += AML_SPIFC_CACHE_SIZE) {
+               trans = min_t(size_t, (len - offset), AML_SPIFC_CACHE_SIZE);
+               memcpy(dout, write_buf + offset, trans);
+               ret = aml_spifc_transfer(spi_flash, dout,
+                               trans, AML_SPIFC_OP_WRITE);
+               if (ret) {
+                       dev_warn(nor->dev, "spi-nor write error %s %d\n",
+                                        __func__, __LINE__);
+                       aml_spifc_relax(spi_flash);
+                       return ret;
+               }
+       }
+       aml_spifc_relax(spi_flash);
+       return len;
+}
+
+static int aml_spifc_hw_init(struct aml_spi_flash *spi_flash)
+{
+       unsigned int div, clk;
+
+       amlsf_set_1bit(SLAVE_SW_RESET, spi_flash->regbase +
+                                 AML_SPIFC_SLAVE);
+       /* do not compatible to applo */
+       amlsf_clr_1bit(USER_COMPATIBLE, spi_flash->regbase +
+                                AML_SPIFC_USER);
+       amlsf_clr_1bit(SLAVE_DEV_MODE, spi_flash->regbase +
+                                AML_SPIFC_SLAVE);
+       div = AML_SPIFC_MAX_CLK_RATE / spi_flash->clkrate;
+       if (div < 2)
+               div = 2UL;
+       if (div > 0x3f)
+               div = 0x3fUL;
+       amlsf_clr_reg(spi_flash->regbase +
+                                 AML_SPIFC_CLK);
+       clk = (div - 1) << CLK_DIV_CNT;
+       clk |= ((div >> 1) - 1) << CLK_HDIV_CNT;
+       clk |= (div - 1) << CLK_LDIV_CNT;
+       writel(clk, spi_flash->regbase +
+                  AML_SPIFC_CLK);
+       return 0;
+}
+
+static int aml_spifc_init(struct aml_spi_flash *spi_flash,
+struct device_node *np)
+{
+       struct device *dev = spi_flash->dev;
+       struct spi_nor *nor;
+       struct mtd_info *mtd;
+       unsigned int read_caps = 0;
+       int ret;
+
+       nor = devm_kzalloc(dev, sizeof(*nor), GFP_KERNEL);
+       if (!nor)
+               return -ENOMEM;
+
+       if (!np)
+               pr_info("np is a null node\n");
+       nor->dev = dev;
+       spi_nor_set_flash_node(nor, np);
+
+       ret = of_property_read_u32(np, "spifc-frequency",
+                       &spi_flash->clkrate);
+       if (ret) {
+               dev_err(dev, "There's no spifc-frequency property for %pOF\n",
+                       np);
+               return ret;
+       }
+       spi_flash->cs = of_get_named_gpio(np, "cs_gpios", 0);
+       gpio_free(spi_flash->cs);
+       gpio_request(spi_flash->cs, "spifc-cs");
+       gpio_direction_output(spi_flash->cs, 1);
+
+       aml_spifc_hw_init(spi_flash);
+       ret = of_property_read_u32(np, "read-capability", &read_caps);
+       spi_flash->nor = nor;
+       nor->priv = spi_flash;
+
+       nor->prepare = aml_spifc_prep;
+       nor->unprepare = aml_spifc_unprep;
+       nor->read_reg = aml_spifc_read_reg;
+       nor->write_reg = aml_spifc_write_reg;
+       nor->read = aml_spifc_read;
+       nor->write = aml_spifc_write;
+       nor->erase = NULL;
+       ret = spi_nor_scan(nor, NULL, read_caps);
+       if (ret)
+               return ret;
+
+       mtd = &nor->mtd;
+       mtd->name = (np->name)?np->name:"amlogic spi-nor";
+       ret = mtd_device_register(mtd, NULL, 0);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+static int aml_spifc_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct resource *res;
+       struct aml_spi_flash *spi_flash;
+       struct device_node *np;
+       int ret;
+
+       spi_flash = devm_kzalloc(dev, sizeof(*spi_flash), GFP_KERNEL);
+       if (!spi_flash)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, spi_flash);
+       spi_flash->dev = dev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       spi_flash->regbase = devm_ioremap_resource(dev, res);
+       if (IS_ERR(spi_flash->regbase))
+               return PTR_ERR(spi_flash->regbase);
+#ifdef AML_SPIFC_AHB
+       res = platform_get_resource_byname(pdev,
+                       IORESOURCE_MEM, "ahb_register");
+       spi_flash->ahbbase = devm_ioremap_resource(dev, res);
+       if (IS_ERR(spi_flash->ahbbase))
+               return PTR_ERR(spi_flash->ahbbase);
+#endif
+
+       spi_flash->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(spi_flash->clk))
+               return PTR_ERR(spi_flash->clk);
+
+       ret = clk_prepare_enable(spi_flash->clk);
+       if (ret)
+               goto err_out;
+
+       mutex_init(&spi_flash->lock);
+       np = of_get_next_available_child(pdev->dev.of_node, NULL);
+       if (!np) {
+               dev_err(&pdev->dev, "no aml-spifc to configure\n");
+               goto err_out;
+       }
+       ret = aml_spifc_init(spi_flash, np);
+
+err_out:
+       if (ret)
+               mutex_destroy(&spi_flash->lock);
+       clk_disable_unprepare(spi_flash->clk);
+       return ret;
+}
+
+static int aml_spifc_remove(struct platform_device *pdev)
+{
+       struct aml_spi_flash *spi_flash = platform_get_drvdata(pdev);
+
+       mtd_device_unregister(&(spi_flash->nor->mtd));
+       mutex_destroy(&spi_flash->lock);
+       clk_disable_unprepare(spi_flash->clk);
+       return 0;
+}
+
+static const struct of_device_id aml_spi_nor_dt_ids[] = {
+       { .compatible = "amlogic,aml-spi-nor"},
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, aml_spi_nor_dt_ids);
+
+static struct platform_driver aml_spifc_driver = {
+       .driver = {
+               .name   = "amlogic-spifc",
+               .of_match_table = aml_spi_nor_dt_ids,
+       },
+       .probe  = aml_spifc_probe,
+       .remove = aml_spifc_remove,
+};
+module_platform_driver(aml_spifc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Amlogic SPI Nor Flash Controller Driver");
+