summaryrefslogtreecommitdiff
authorYi Zeng <yi.zeng@amlogic.com>2018-08-02 06:22:04 (GMT)
committer Yixun Lan <yixun.lan@amlogic.com>2018-08-07 05:47:48 (GMT)
commit8e6dad10fbd8092f3f95fb427daf57c614b0fb2e (patch)
tree686a4bc112d4dc2303ba6cf433ef50f97c6bdcf9
parent791bd6209e1b5c0c51bdf9c20c00e006d021ee4d (diff)
downloadcommon-8e6dad10fbd8092f3f95fb427daf57c614b0fb2e.zip
common-8e6dad10fbd8092f3f95fb427daf57c614b0fb2e.tar.gz
common-8e6dad10fbd8092f3f95fb427daf57c614b0fb2e.tar.bz2
mtd: spifc: add spifc support for txl
PD#171041: mtd: spifc: add spifc support for txl Change-Id: I487161c6e85e3b232ed0c3891784b5a37f6d878c Signed-off-by: Yi Zeng <yi.zeng@amlogic.com>
Diffstat
-rw-r--r--Documentation/devicetree/bindings/mtd/aml-spifc.txt27
-rw-r--r--MAINTAINERS7
-rw-r--r--arch/arm64/boot/dts/amlogic/mesontxl.dtsi37
-rw-r--r--arch/arm64/boot/dts/amlogic/txl_t962_p321.dts17
-rw-r--r--drivers/amlogic/Kconfig2
-rw-r--r--drivers/amlogic/Makefile2
-rw-r--r--drivers/amlogic/spi-nor/Kconfig16
-rw-r--r--drivers/amlogic/spi-nor/Makefile5
-rw-r--r--drivers/amlogic/spi-nor/aml-spifc.c629
9 files changed, 742 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/mtd/aml-spifc.txt b/Documentation/devicetree/bindings/mtd/aml-spifc.txt
new file mode 100644
index 0000000..8c11cf3
--- a/dev/null
+++ b/Documentation/devicetree/bindings/mtd/aml-spifc.txt
@@ -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
diff --git a/MAINTAINERS b/MAINTAINERS
index dd49885..388d95d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
diff --git a/arch/arm64/boot/dts/amlogic/mesontxl.dtsi b/arch/arm64/boot/dts/amlogic/mesontxl.dtsi
index dba40c9..cf657a9 100644
--- a/arch/arm64/boot/dts/amlogic/mesontxl.dtsi
+++ b/arch/arm64/boot/dts/amlogic/mesontxl.dtsi
@@ -1320,6 +1320,43 @@
};
};
+ 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",
diff --git a/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts b/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
index c4db667..447176e 100644
--- a/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
+++ b/arch/arm64/boot/dts/amlogic/txl_t962_p321.dts
@@ -542,6 +542,23 @@
};
};
+ 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";
diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig
index ea7a11b..d1a1afb 100644
--- a/drivers/amlogic/Kconfig
+++ b/drivers/amlogic/Kconfig
@@ -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
diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile
index e28869c..801a195 100644
--- a/drivers/amlogic/Makefile
+++ b/drivers/amlogic/Makefile
@@ -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
index 0000000..f5f61a30
--- a/dev/null
+++ b/drivers/amlogic/spi-nor/Kconfig
@@ -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
index 0000000..f753cc8
--- a/dev/null
+++ b/drivers/amlogic/spi-nor/Makefile
@@ -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
index 0000000..977ab64
--- a/dev/null
+++ b/drivers/amlogic/spi-nor/aml-spifc.c
@@ -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");
+