430 lines
10 KiB
C
430 lines
10 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <clk.h>
|
|
#include <asm/cache.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include <dm/root.h>
|
|
#include <dm/lists.h>
|
|
#include <dm/util.h>
|
|
#include <dm/pinctrl.h>
|
|
#include <spi.h>
|
|
#include <amlogic/aml_pageinfo.h>
|
|
#include "page_info.h"
|
|
|
|
//#define __SPI_NFC_DEBUG__
|
|
|
|
#ifdef __SPI_NFC_DEBUG__
|
|
#define SPI_NFC_DEBUG(...) pr_info(__VA_ARGS__)
|
|
#else
|
|
#define SPI_NFC_DEBUG(...)
|
|
#endif
|
|
|
|
unsigned char disable_host_ecc;
|
|
|
|
struct spi_nfc_priv {
|
|
unsigned char save_cmd;
|
|
u32 save_addr;
|
|
u32 save_addr_len;
|
|
|
|
u32 spi_cfg;
|
|
u32 nand_cfg;
|
|
u32 sd_emmc_clk;
|
|
|
|
u8 *data_buf;
|
|
u8 *info_buf;
|
|
};
|
|
|
|
struct spi_nfc_platdata {
|
|
u32 frequency_index;
|
|
};
|
|
|
|
#define DATA_BUF_SIZE (4096)
|
|
#define OOB_BUF_SIZE (128)
|
|
#define SPI_NFC_BUF_SIZE (DATA_BUF_SIZE + OOB_BUF_SIZE)
|
|
|
|
static int spi_nfc_probe(struct udevice *bus)
|
|
{
|
|
struct spi_nfc_priv *priv = dev_get_priv(bus);
|
|
int ret;
|
|
|
|
ret = pinctrl_select_state(bus, "default");
|
|
if (ret) {
|
|
pr_err("select state %s failed\n", "default");
|
|
return ret;
|
|
}
|
|
|
|
priv->data_buf = kzalloc(SPI_NFC_BUF_SIZE, GFP_KERNEL);
|
|
if (!priv->data_buf)
|
|
return -1;
|
|
|
|
priv->info_buf = priv->data_buf + DATA_BUF_SIZE;
|
|
|
|
page_info_pre_init();
|
|
page_info_initialize(DEFAULT_ECC_MODE, 0, 0);
|
|
nfc_set_clock_and_timing(NFC_STATUS_OFF, SPINAND_FLASH);
|
|
|
|
#ifdef CONFIG_AML_SPI_NFC_DISABLE_HOST_ECC
|
|
disable_host_ecc = 1;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_ofdata_to_platdata(struct udevice *bus)
|
|
{
|
|
struct spi_nfc_platdata *plat = dev_get_platdata(bus);
|
|
const void *blob = gd->fdt_blob;
|
|
int node = dev_of_offset(bus);
|
|
|
|
plat->frequency_index = fdtdec_get_uint(blob, node,
|
|
"clock_index", 0xFF);
|
|
return 0;
|
|
}
|
|
|
|
static void spi_nfc_auto_oob_ops(u8 *info, u8 *oob_pos, int ecc_steps, bool set)
|
|
{
|
|
u8 *src, *dst, i;
|
|
|
|
for (i = 0; i < ecc_steps; i++) {
|
|
src = (set) ? (info + 2 * i) : (oob_pos + i * (2 + 14));
|
|
dst = (set) ? (oob_pos + i * (2 + 14)) : (info + 2 * i);
|
|
memcpy(dst, src, 2);
|
|
}
|
|
}
|
|
|
|
static void spi_nfc_covert_buf_to_user(struct spi_nfc_priv *priv,
|
|
u8 *user_buf,
|
|
u8 *oob_pos, u8 ecc_steps,
|
|
bool oob_only, bool auto_oob,
|
|
bool oob_required)
|
|
{
|
|
u8 *buf = priv->data_buf, *info_buf = priv->info_buf;
|
|
u8 oob_temp[OOB_BUF_SIZE] = {0};
|
|
u32 page_size = page_info_get_page_size();
|
|
|
|
SPI_NFC_DEBUG("oob only %s\n", (oob_only) ? "yes" : "no");
|
|
SPI_NFC_DEBUG("oob is %s\n", (oob_required) ? "required" : "not required");
|
|
SPI_NFC_DEBUG("auto_oob is %s\n", (auto_oob) ? "set" : "not set");
|
|
if (oob_only || oob_required) {
|
|
nfc_get_user_byte(oob_temp, (u64 *)info_buf, ecc_steps);
|
|
DUMP_BUFFER(info_buf, 64, 4, 16);
|
|
if (auto_oob)
|
|
spi_nfc_auto_oob_ops(oob_temp, oob_pos,
|
|
ecc_steps, true);
|
|
else
|
|
memcpy(oob_pos, oob_temp, ecc_steps * 2);
|
|
}
|
|
|
|
if (oob_only)
|
|
return;
|
|
|
|
memcpy((u8 *)user_buf, buf, page_size);
|
|
DUMP_BUFFER(user_buf, page_size, page_size / 512, 16);
|
|
}
|
|
|
|
static void spi_nfc_covert_buf_to_host(struct spi_nfc_priv *priv,
|
|
u8 *user_buf,
|
|
u8 *oob_pos, u8 ecc_steps,
|
|
bool oob_only, bool auto_oob,
|
|
bool oob_required)
|
|
{
|
|
u8 *buf = priv->data_buf, *info_buf = priv->info_buf;
|
|
u8 oob_temp[OOB_BUF_SIZE] = {0};
|
|
u32 page_size = page_info_get_page_size();
|
|
|
|
SPI_NFC_DEBUG("oob only %s\n", (oob_only) ? "yes" : "no");
|
|
SPI_NFC_DEBUG("oob is %s\n", (oob_required) ? "required" : "not required");
|
|
SPI_NFC_DEBUG("auto_oob is %s\n", (auto_oob) ? "set" : "not set");
|
|
|
|
DUMP_BUFFER(oob_pos, 64, 4, 16);
|
|
if (oob_only || oob_required) {
|
|
if (auto_oob)
|
|
spi_nfc_auto_oob_ops(oob_temp, oob_pos,
|
|
ecc_steps, false);
|
|
else
|
|
memcpy(oob_temp, oob_pos, ecc_steps * 2);
|
|
}
|
|
|
|
memset(info_buf, 0, 64);
|
|
nfc_set_user_byte(oob_temp, (u64 *)info_buf, ecc_steps);
|
|
|
|
if (oob_only)
|
|
return;
|
|
|
|
memcpy(buf, user_buf, page_size);
|
|
DUMP_BUFFER(buf, page_size, page_size / 512, 16);
|
|
}
|
|
|
|
static int spi_nfc_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *oobregion)
|
|
{
|
|
if (section >= (mtd->writesize >> 9))
|
|
return -ERANGE;
|
|
|
|
oobregion->offset = 2 + (section * (2 + 14));
|
|
oobregion->length = 14;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_ooblayout_free(struct mtd_info *mtd, int section,
|
|
struct mtd_oob_region *oobregion)
|
|
{
|
|
if (section >= (mtd->writesize >> 9))
|
|
return -ERANGE;
|
|
|
|
oobregion->offset = section * (2 + 14);
|
|
oobregion->length = 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mtd_ooblayout_ops spi_nfc_ecc_ooblayout = {
|
|
.ecc = spi_nfc_ooblayout_ecc,
|
|
.free = spi_nfc_ooblayout_free,
|
|
};
|
|
|
|
static void spi_nfc_xfer_prepare(struct udevice *dev)
|
|
{
|
|
struct dm_spi_slave_platdata *plat;
|
|
struct mtd_info *mtd;
|
|
|
|
if (disable_host_ecc)
|
|
return;
|
|
|
|
plat = dev_get_parent_platdata(dev);
|
|
if (!plat->cs) {
|
|
disable_host_ecc = 1;
|
|
return;
|
|
}
|
|
|
|
if (!page_info_get_block_size()) {
|
|
mtd = dev_get_uclass_priv(dev);
|
|
page_info->dev_cfg0.page_size = mtd->writesize;
|
|
page_info->dev_cfg1.block_size = mtd->erasesize;
|
|
page_info->host_cfg.n2m_cmd =
|
|
(DEFAULT_ECC_MODE & (~0x3F)) | mtd->writesize >> 9;
|
|
SPI_NFC_DEBUG("page_size = 0x%x block_size = 0x%x\n",
|
|
page_info->dev_cfg0.page_size,
|
|
page_info->dev_cfg1.block_size);
|
|
mtd_set_ooblayout(mtd, &spi_nfc_ecc_ooblayout);
|
|
mtd->oobavail = (mtd->writesize / 512) * 2;
|
|
}
|
|
}
|
|
|
|
static int spi_nfc_dma_xfer(struct spi_nfc_priv *priv,
|
|
unsigned long flags,
|
|
u8 *user_buf, int len, bool read)
|
|
{
|
|
u32 page_size, n2m_cmd;
|
|
bool raw, oob_only, auto_oob, oob_required;
|
|
u8 *buf, *oob_pos, ecc_steps;
|
|
int ret = 0;
|
|
enum dma_data_direction direction;
|
|
dma_addr_t dma_addr_data, dma_addr_info;
|
|
|
|
oob_only = ((flags & SPI_XFER_OOB_ONLY) != 0);
|
|
oob_required = ((flags & SPI_XFER_OOB) != 0);
|
|
raw = ((flags & SPI_XFER_RAW) != 0);
|
|
auto_oob = ((flags & SPI_XFER_AUTO_OOB) != 0);
|
|
|
|
SPI_NFC_DEBUG("flags = %lx user_buf = %p len = 0x%x disable_host_ecc = %d %s\n",
|
|
flags, user_buf, len, disable_host_ecc,
|
|
(read) ? "read" : "write");
|
|
|
|
page_size = page_info_get_page_size();
|
|
oob_pos = (oob_only) ? user_buf : (user_buf + page_size);
|
|
|
|
if (raw || disable_host_ecc) {
|
|
buf = user_buf;
|
|
nfc_raw_size_ext_convert(len);
|
|
n2m_cmd = (read) ? N2M_RAW : M2N_RAW;
|
|
n2m_cmd |= (len & ((1 << NFC_RAW_CHUNK_SHIFT) - 1));
|
|
ecc_steps = 1;
|
|
} else {
|
|
buf = priv->data_buf;
|
|
n2m_cmd = nfc_recalculate_n2m_command(len, 1);
|
|
ecc_steps = n2m_cmd & 0x3F;
|
|
len = SPI_NFC_BUF_SIZE;
|
|
if (!read) {
|
|
n2m_cmd &= ~(N2M_RAW ^ M2N_RAW);
|
|
spi_nfc_covert_buf_to_host(priv, user_buf,
|
|
oob_pos, ecc_steps,
|
|
oob_only, auto_oob,
|
|
oob_required);
|
|
}
|
|
}
|
|
|
|
flush_dcache_range((unsigned long)buf,
|
|
(unsigned long)buf + len);
|
|
|
|
SPI_NFC_DEBUG("n2m_cmd = %x ecc_steps = %x save_addr = %x addr_len = %x\n",
|
|
n2m_cmd, ecc_steps, priv->save_addr, priv->save_addr_len);
|
|
|
|
if (!disable_host_ecc)
|
|
priv->save_addr &= ~((page_size << ((read) ? 9 : 1)) - 1);
|
|
|
|
ret = NFC_SEND_CMD_ADDR(priv->save_cmd,
|
|
(uint8_t *)&priv->save_addr,
|
|
priv->save_addr_len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
direction = (read) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
|
|
dma_addr_data = dma_map_single(buf, len, direction);
|
|
dma_addr_info = dma_map_single(priv->info_buf, 64, direction);
|
|
|
|
nfc_set_dma_mem_and_info((unsigned long)dma_addr_data,
|
|
(unsigned long)dma_addr_info);
|
|
|
|
ret = nfc_start_dma_and_wait_done(n2m_cmd);
|
|
if (ret || !read)
|
|
return ret;
|
|
|
|
ret = nfc_wait_data_and_ecc_engine_done((u64 *)priv->info_buf,
|
|
ecc_steps, raw);
|
|
if (ret) {
|
|
SPI_NFC_DEBUG("ecc error dump:\n");
|
|
DUMP_BUFFER(priv->info_buf, 64, 4, 16);
|
|
DUMP_BUFFER(buf, page_size, page_size / 16, 16);
|
|
return ret;
|
|
}
|
|
|
|
dma_unmap_single(dma_addr_data, len, direction);
|
|
dma_unmap_single(dma_addr_info, 64, direction);
|
|
|
|
if (raw || disable_host_ecc) {
|
|
DUMP_BUFFER(buf, len, len / 16, 16);
|
|
return 0;
|
|
}
|
|
|
|
spi_nfc_covert_buf_to_user(priv, user_buf,
|
|
oob_pos, ecc_steps,
|
|
oob_only, auto_oob,
|
|
oob_required);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_xfer(struct udevice *dev,
|
|
u32 bitlen, const void *dout,
|
|
void *din, unsigned long flags)
|
|
{
|
|
struct udevice *bus = dev->parent;
|
|
struct spi_nfc_priv *priv = dev_get_priv(bus);
|
|
int len = bitlen >> 3;
|
|
u8 *buf = (u8 *)dout;
|
|
int ret = 0, i = 0;
|
|
|
|
writel(priv->spi_cfg, SPI_CFG);
|
|
writel(priv->nand_cfg, NAND_CFG);
|
|
writel(priv->sd_emmc_clk, SD_EMMC_CLK);
|
|
|
|
if (flags & SPI_XFER_BEGIN) {
|
|
priv->save_addr = 0;
|
|
priv->save_addr_len = len - 1;
|
|
for (i = 0; i < priv->save_addr_len; i++) {
|
|
priv->save_addr <<= 8;
|
|
priv->save_addr |= buf[i + 1];
|
|
}
|
|
|
|
if (flags & SPI_XFER_END) {
|
|
/* simple case , only command and address */
|
|
if (priv->save_addr_len)
|
|
ret = NFC_SEND_CMD_ADDR(buf[0],
|
|
(uint8_t *)&priv->save_addr,
|
|
len - 1);
|
|
else
|
|
ret = NFC_SEND_CMD(buf[0]);
|
|
return ret;
|
|
}
|
|
/* command with data */
|
|
priv->save_cmd = buf[0];
|
|
return 0;
|
|
}
|
|
|
|
if (din)
|
|
buf = (u8 *)din;
|
|
|
|
if (len <= SPINAND_MAX_ID_LEN + 2) {
|
|
nfc_set_data_bus_width(0);
|
|
if (din)
|
|
ret = NFC_SEND_CMD_ADDR_DATA_RD(priv->save_cmd,
|
|
(uint8_t *)&priv->save_addr,
|
|
priv->save_addr_len, buf, len);
|
|
else
|
|
ret = NFC_SEND_CMD_ADDR_DATA_WR(priv->save_cmd,
|
|
(uint8_t *)&priv->save_addr,
|
|
priv->save_addr_len, buf, len);
|
|
return ret;
|
|
}
|
|
|
|
spi_nfc_xfer_prepare(dev);
|
|
spi_nfc_dma_xfer(priv, flags, buf, len, din);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_set_speed(struct udevice *bus, uint hz)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_set_mode(struct udevice *bus, uint mode)
|
|
{
|
|
struct spi_nfc_priv *priv = dev_get_priv(bus);
|
|
|
|
if (mode & SPI_RX_QUAD)
|
|
page_info->dev_cfg0.bus_width |= 2;
|
|
else if (mode & SPI_RX_DUAL)
|
|
page_info->dev_cfg0.bus_width |= 1;
|
|
|
|
nfc_set_clock_and_timing(NFC_STATUS_OFF, SPINAND_FLASH);
|
|
|
|
priv->spi_cfg = readl(SPI_CFG);
|
|
priv->nand_cfg = readl(NAND_CFG);
|
|
priv->sd_emmc_clk = readl(SD_EMMC_CLK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nfc_set_wordlen(struct udevice *bus, u32 wordlen)
|
|
{
|
|
if (wordlen != 8)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id spi_nfc_ids[] = {
|
|
{ .compatible = "amlogic,spi_nfc" },
|
|
{ }
|
|
};
|
|
|
|
static struct dm_spi_ops spi_nfc_ops = {
|
|
.xfer = spi_nfc_xfer,
|
|
.set_speed = spi_nfc_set_speed,
|
|
.set_mode = spi_nfc_set_mode,
|
|
.set_wordlen = spi_nfc_set_wordlen,
|
|
};
|
|
|
|
U_BOOT_DRIVER(spi_nfc) = {
|
|
.name = "spi_nfc",
|
|
.id = UCLASS_SPI,
|
|
.of_match = spi_nfc_ids,
|
|
.ofdata_to_platdata = spi_nfc_ofdata_to_platdata,
|
|
.platdata_auto_alloc_size = sizeof(struct spi_nfc_platdata),
|
|
.priv_auto_alloc_size = sizeof(struct spi_nfc_priv),
|
|
.per_child_auto_alloc_size = sizeof(struct spi_slave),
|
|
.ops = &spi_nfc_ops,
|
|
.probe = spi_nfc_probe,
|
|
};
|