author | Yi 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) |
commit | 8e6dad10fbd8092f3f95fb427daf57c614b0fb2e (patch) | |
tree | 686a4bc112d4dc2303ba6cf433ef50f97c6bdcf9 | |
parent | 791bd6209e1b5c0c51bdf9c20c00e006d021ee4d (diff) | |
download | common-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>
-rw-r--r-- | Documentation/devicetree/bindings/mtd/aml-spifc.txt | 27 | ||||
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | arch/arm64/boot/dts/amlogic/mesontxl.dtsi | 37 | ||||
-rw-r--r-- | arch/arm64/boot/dts/amlogic/txl_t962_p321.dts | 17 | ||||
-rw-r--r-- | drivers/amlogic/Kconfig | 2 | ||||
-rw-r--r-- | drivers/amlogic/Makefile | 2 | ||||
-rw-r--r-- | drivers/amlogic/spi-nor/Kconfig | 16 | ||||
-rw-r--r-- | drivers/amlogic/spi-nor/Makefile | 5 | ||||
-rw-r--r-- | drivers/amlogic/spi-nor/aml-spifc.c | 629 |
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"); + |