|
|
|
|
@@ -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");
|
|
|
|
|
|