mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-05 11:00:15 +00:00
41575d8e4c
This construct is quite long-winded. In earlier days it made some sense since auto-allocation was a strange concept. But with driver model now used pretty universally, we can shorten this to 'auto'. This reduces verbosity and makes it easier to read. Coincidentally it also ensures that every declaration is on one line, thus making dtoc's job easier. Signed-off-by: Simon Glass <sjg@chromium.org>
425 lines
9.8 KiB
C
425 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2018 Marvell International Ltd.
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <dm/of_access.h>
|
|
#include <malloc.h>
|
|
#include <memalign.h>
|
|
#include <nand.h>
|
|
#include <pci.h>
|
|
#include <pci_ids.h>
|
|
#include <time.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand_bch.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <asm/io.h>
|
|
#include <asm/types.h>
|
|
#include <asm/dma-mapping.h>
|
|
#include <asm/arch/clock.h>
|
|
#include "octeontx_bch.h"
|
|
|
|
#ifdef DEBUG
|
|
# undef CONFIG_LOGLEVEL
|
|
# define CONFIG_LOGLEVEL 8
|
|
#endif
|
|
|
|
LIST_HEAD(octeontx_bch_devices);
|
|
static unsigned int num_vfs = BCH_NR_VF;
|
|
static void *bch_pf;
|
|
static void *bch_vf;
|
|
static void *token;
|
|
static bool bch_pf_initialized;
|
|
static bool bch_vf_initialized;
|
|
|
|
static int pci_enable_sriov(struct udevice *dev, int nr_virtfn)
|
|
{
|
|
int ret;
|
|
|
|
ret = pci_sriov_init(dev, nr_virtfn);
|
|
if (ret)
|
|
printf("%s(%s): pci_sriov_init returned %d\n", __func__,
|
|
dev->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
void *octeontx_bch_getv(void)
|
|
{
|
|
if (!bch_vf)
|
|
return NULL;
|
|
if (bch_vf_initialized && bch_pf_initialized)
|
|
return bch_vf;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void octeontx_bch_putv(void *token)
|
|
{
|
|
bch_vf_initialized = !!token;
|
|
bch_vf = token;
|
|
}
|
|
|
|
void *octeontx_bch_getp(void)
|
|
{
|
|
return token;
|
|
}
|
|
|
|
void octeontx_bch_putp(void *token)
|
|
{
|
|
bch_pf = token;
|
|
bch_pf_initialized = !!token;
|
|
}
|
|
|
|
static int do_bch_init(struct bch_device *bch)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void bch_reset(struct bch_device *bch)
|
|
{
|
|
writeq(1, bch->reg_base + BCH_CTL);
|
|
mdelay(2);
|
|
}
|
|
|
|
static void bch_disable(struct bch_device *bch)
|
|
{
|
|
writeq(~0ull, bch->reg_base + BCH_ERR_INT_ENA_W1C);
|
|
writeq(~0ull, bch->reg_base + BCH_ERR_INT);
|
|
bch_reset(bch);
|
|
}
|
|
|
|
static u32 bch_check_bist_status(struct bch_device *bch)
|
|
{
|
|
return readq(bch->reg_base + BCH_BIST_RESULT);
|
|
}
|
|
|
|
static int bch_device_init(struct bch_device *bch)
|
|
{
|
|
u64 bist;
|
|
int rc;
|
|
|
|
debug("%s: Resetting...\n", __func__);
|
|
/* Reset the PF when probed first */
|
|
bch_reset(bch);
|
|
|
|
debug("%s: Checking BIST...\n", __func__);
|
|
/* Check BIST status */
|
|
bist = (u64)bch_check_bist_status(bch);
|
|
if (bist) {
|
|
dev_err(dev, "BCH BIST failed with code 0x%llx\n", bist);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Get max VQs/VFs supported by the device */
|
|
|
|
bch->max_vfs = pci_sriov_get_totalvfs(bch->dev);
|
|
debug("%s: %d vfs\n", __func__, bch->max_vfs);
|
|
if (num_vfs > bch->max_vfs) {
|
|
dev_warn(dev, "Num of VFs to enable %d is greater than max available. Enabling %d VFs.\n",
|
|
num_vfs, bch->max_vfs);
|
|
num_vfs = bch->max_vfs;
|
|
}
|
|
bch->vfs_enabled = bch->max_vfs;
|
|
/* Get number of VQs/VFs to be enabled */
|
|
/* TODO: Get CLK frequency */
|
|
/* Reset device parameters */
|
|
|
|
debug("%s: Doing initialization\n", __func__);
|
|
rc = do_bch_init(bch);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int bch_sriov_configure(struct udevice *dev, int numvfs)
|
|
{
|
|
struct bch_device *bch = dev_get_priv(dev);
|
|
int ret = -EBUSY;
|
|
|
|
debug("%s(%s, %d), bch: %p, vfs_in_use: %d, enabled: %d\n", __func__,
|
|
dev->name, numvfs, bch, bch->vfs_in_use, bch->vfs_enabled);
|
|
if (bch->vfs_in_use)
|
|
goto exit;
|
|
|
|
ret = 0;
|
|
|
|
if (numvfs > 0) {
|
|
debug("%s: Enabling sriov\n", __func__);
|
|
ret = pci_enable_sriov(dev, numvfs);
|
|
if (ret == 0) {
|
|
bch->flags |= BCH_FLAG_SRIOV_ENABLED;
|
|
ret = numvfs;
|
|
bch->vfs_enabled = numvfs;
|
|
}
|
|
}
|
|
|
|
debug("VFs enabled: %d\n", ret);
|
|
exit:
|
|
debug("%s: Returning %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int octeontx_pci_bchpf_probe(struct udevice *dev)
|
|
{
|
|
struct bch_device *bch;
|
|
int ret;
|
|
|
|
debug("%s(%s)\n", __func__, dev->name);
|
|
bch = dev_get_priv(dev);
|
|
if (!bch)
|
|
return -ENOMEM;
|
|
|
|
bch->reg_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
|
|
bch->dev = dev;
|
|
|
|
debug("%s: base address: %p\n", __func__, bch->reg_base);
|
|
ret = bch_device_init(bch);
|
|
if (ret) {
|
|
printf("%s(%s): init returned %d\n", __func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
INIT_LIST_HEAD(&bch->list);
|
|
list_add(&bch->list, &octeontx_bch_devices);
|
|
token = (void *)dev;
|
|
|
|
debug("%s: Configuring SRIOV\n", __func__);
|
|
bch_sriov_configure(dev, num_vfs);
|
|
debug("%s: Done.\n", __func__);
|
|
octeontx_bch_putp(bch);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pci_device_id octeontx_bchpf_pci_id_table[] = {
|
|
{ PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_BCH) },
|
|
{},
|
|
};
|
|
|
|
static const struct pci_device_id octeontx_bchvf_pci_id_table[] = {
|
|
{ PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_BCHVF)},
|
|
{},
|
|
};
|
|
|
|
/**
|
|
* Given a data block calculate the ecc data and fill in the response
|
|
*
|
|
* @param[in] block 8-byte aligned pointer to data block to calculate ECC
|
|
* @param block_size Size of block in bytes, must be a multiple of two.
|
|
* @param bch_level Number of errors that must be corrected. The number of
|
|
* parity bytes is equal to ((15 * bch_level) + 7) / 8.
|
|
* Must be 4, 8, 16, 24, 32, 40, 48, 56, 60 or 64.
|
|
* @param[out] ecc 8-byte aligned pointer to where ecc data should go
|
|
* @param[in] resp pointer to where responses will be written.
|
|
*
|
|
* @return Zero on success, negative on failure.
|
|
*/
|
|
int octeontx_bch_encode(struct bch_vf *vf, dma_addr_t block, u16 block_size,
|
|
u8 bch_level, dma_addr_t ecc, dma_addr_t resp)
|
|
{
|
|
union bch_cmd cmd;
|
|
int rc;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.s.cword.ecc_gen = eg_gen;
|
|
cmd.s.cword.ecc_level = bch_level;
|
|
cmd.s.cword.size = block_size;
|
|
|
|
cmd.s.oword.ptr = ecc;
|
|
cmd.s.iword.ptr = block;
|
|
cmd.s.rword.ptr = resp;
|
|
rc = octeontx_cmd_queue_write(QID_BCH, 1,
|
|
sizeof(cmd) / sizeof(uint64_t), cmd.u);
|
|
if (rc)
|
|
return -1;
|
|
|
|
octeontx_bch_write_doorbell(1, vf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Given a data block and ecc data correct the data block
|
|
*
|
|
* @param[in] block_ecc_in 8-byte aligned pointer to data block with ECC
|
|
* data concatenated to the end to correct
|
|
* @param block_size Size of block in bytes, must be a multiple of
|
|
* two.
|
|
* @param bch_level Number of errors that must be corrected. The
|
|
* number of parity bytes is equal to
|
|
* ((15 * bch_level) + 7) / 8.
|
|
* Must be 4, 8, 16, 24, 32, 40, 48, 56, 60 or 64.
|
|
* @param[out] block_out 8-byte aligned pointer to corrected data buffer.
|
|
* This should not be the same as block_ecc_in.
|
|
* @param[in] resp pointer to where responses will be written.
|
|
*
|
|
* @return Zero on success, negative on failure.
|
|
*/
|
|
|
|
int octeontx_bch_decode(struct bch_vf *vf, dma_addr_t block_ecc_in,
|
|
u16 block_size, u8 bch_level,
|
|
dma_addr_t block_out, dma_addr_t resp)
|
|
{
|
|
union bch_cmd cmd;
|
|
int rc;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.s.cword.ecc_gen = eg_correct;
|
|
cmd.s.cword.ecc_level = bch_level;
|
|
cmd.s.cword.size = block_size;
|
|
|
|
cmd.s.oword.ptr = block_out;
|
|
cmd.s.iword.ptr = block_ecc_in;
|
|
cmd.s.rword.ptr = resp;
|
|
rc = octeontx_cmd_queue_write(QID_BCH, 1,
|
|
sizeof(cmd) / sizeof(uint64_t), cmd.u);
|
|
if (rc)
|
|
return -1;
|
|
|
|
octeontx_bch_write_doorbell(1, vf);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(octeontx_bch_decode);
|
|
|
|
int octeontx_bch_wait(struct bch_vf *vf, union bch_resp *resp,
|
|
dma_addr_t handle)
|
|
{
|
|
ulong start = get_timer(0);
|
|
|
|
__iormb(); /* HW is updating *resp */
|
|
while (!resp->s.done && get_timer(start) < 10)
|
|
__iormb(); /* HW is updating *resp */
|
|
|
|
if (resp->s.done)
|
|
return 0;
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
struct bch_q octeontx_bch_q[QID_MAX];
|
|
|
|
static int octeontx_cmd_queue_initialize(struct udevice *dev, int queue_id,
|
|
int max_depth, int fpa_pool,
|
|
int pool_size)
|
|
{
|
|
/* some params are for later merge with CPT or cn83xx */
|
|
struct bch_q *q = &octeontx_bch_q[queue_id];
|
|
unsigned long paddr;
|
|
u64 *chunk_buffer;
|
|
int chunk = max_depth + 1;
|
|
int i, size;
|
|
|
|
if ((unsigned int)queue_id >= QID_MAX)
|
|
return -EINVAL;
|
|
if (max_depth & chunk) /* must be 2^N - 1 */
|
|
return -EINVAL;
|
|
|
|
size = NQS * chunk * sizeof(u64);
|
|
chunk_buffer = dma_alloc_coherent(size, &paddr);
|
|
if (!chunk_buffer)
|
|
return -ENOMEM;
|
|
|
|
q->base_paddr = paddr;
|
|
q->dev = dev;
|
|
q->index = 0;
|
|
q->max_depth = max_depth;
|
|
q->pool_size_m1 = pool_size;
|
|
q->base_vaddr = chunk_buffer;
|
|
|
|
for (i = 0; i < NQS; i++) {
|
|
u64 *ixp;
|
|
int inext = (i + 1) * chunk - 1;
|
|
int j = (i + 1) % NQS;
|
|
int jnext = j * chunk;
|
|
dma_addr_t jbase = q->base_paddr + jnext * sizeof(u64);
|
|
|
|
ixp = &chunk_buffer[inext];
|
|
*ixp = jbase;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int octeontx_pci_bchvf_probe(struct udevice *dev)
|
|
{
|
|
struct bch_vf *vf;
|
|
union bch_vqx_ctl ctl;
|
|
union bch_vqx_cmd_buf cbuf;
|
|
int err;
|
|
|
|
debug("%s(%s)\n", __func__, dev->name);
|
|
vf = dev_get_priv(dev);
|
|
if (!vf)
|
|
return -ENOMEM;
|
|
|
|
vf->dev = dev;
|
|
|
|
/* Map PF's configuration registers */
|
|
vf->reg_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
|
|
debug("%s: reg base: %p\n", __func__, vf->reg_base);
|
|
|
|
err = octeontx_cmd_queue_initialize(dev, QID_BCH, QDEPTH - 1, 0,
|
|
sizeof(union bch_cmd) * QDEPTH);
|
|
if (err) {
|
|
dev_err(dev, "octeontx_cmd_queue_initialize() failed\n");
|
|
goto release;
|
|
}
|
|
|
|
ctl.u = readq(vf->reg_base + BCH_VQX_CTL(0));
|
|
|
|
cbuf.u = 0;
|
|
cbuf.s.ldwb = 1;
|
|
cbuf.s.dfb = 1;
|
|
cbuf.s.size = QDEPTH;
|
|
writeq(cbuf.u, vf->reg_base + BCH_VQX_CMD_BUF(0));
|
|
|
|
writeq(ctl.u, vf->reg_base + BCH_VQX_CTL(0));
|
|
|
|
writeq(octeontx_bch_q[QID_BCH].base_paddr,
|
|
vf->reg_base + BCH_VQX_CMD_PTR(0));
|
|
|
|
octeontx_bch_putv(vf);
|
|
|
|
debug("%s: bch vf initialization complete\n", __func__);
|
|
|
|
if (octeontx_bch_getv())
|
|
return octeontx_pci_nand_deferred_probe();
|
|
|
|
return -1;
|
|
|
|
release:
|
|
return err;
|
|
}
|
|
|
|
static int octeontx_pci_bchpf_remove(struct udevice *dev)
|
|
{
|
|
struct bch_device *bch = dev_get_priv(dev);
|
|
|
|
bch_disable(bch);
|
|
return 0;
|
|
}
|
|
|
|
U_BOOT_DRIVER(octeontx_pci_bchpf) = {
|
|
.name = BCHPF_DRIVER_NAME,
|
|
.id = UCLASS_MISC,
|
|
.probe = octeontx_pci_bchpf_probe,
|
|
.remove = octeontx_pci_bchpf_remove,
|
|
.priv_auto = sizeof(struct bch_device),
|
|
.flags = DM_FLAG_OS_PREPARE,
|
|
};
|
|
|
|
U_BOOT_DRIVER(octeontx_pci_bchvf) = {
|
|
.name = BCHVF_DRIVER_NAME,
|
|
.id = UCLASS_MISC,
|
|
.probe = octeontx_pci_bchvf_probe,
|
|
.priv_auto = sizeof(struct bch_vf),
|
|
};
|
|
|
|
U_BOOT_PCI_DEVICE(octeontx_pci_bchpf, octeontx_bchpf_pci_id_table);
|
|
U_BOOT_PCI_DEVICE(octeontx_pci_bchvf, octeontx_bchvf_pci_id_table);
|