Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: GPL-2.0-only
config MTD_NAND_JZ4780
tristate "JZ4780 NAND controller"
depends on MIPS || COMPILE_TEST
depends on JZ4780_NEMC
help
Enables support for NAND Flash connected to the NEMC on JZ4780 SoC
based boards, using the BCH controller for hardware error correction.
if MTD_NAND_JZ4780
config MTD_NAND_INGENIC_ECC
bool
config MTD_NAND_JZ4740_ECC
tristate "Hardware BCH support for JZ4740 SoC"
select MTD_NAND_INGENIC_ECC
help
Enable this driver to support the Reed-Solomon error-correction
hardware present on the JZ4740 SoC from Ingenic.
This driver can also be built as a module. If so, the module
will be called jz4740-ecc.
config MTD_NAND_JZ4725B_BCH
tristate "Hardware BCH support for JZ4725B SoC"
select MTD_NAND_INGENIC_ECC
help
Enable this driver to support the BCH error-correction hardware
present on the JZ4725B SoC from Ingenic.
This driver can also be built as a module. If so, the module
will be called jz4725b-bch.
config MTD_NAND_JZ4780_BCH
tristate "Hardware BCH support for JZ4780 SoC"
select MTD_NAND_INGENIC_ECC
help
Enable this driver to support the BCH error-correction hardware
present on the JZ4780 SoC from Ingenic.
This driver can also be built as a module. If so, the module
will be called jz4780-bch.
endif # MTD_NAND_JZ4780
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MTD_NAND_JZ4780) += ingenic_nand.o
ingenic_nand-y += ingenic_nand_drv.o
ingenic_nand-$(CONFIG_MTD_NAND_INGENIC_ECC) += ingenic_ecc.o
obj-$(CONFIG_MTD_NAND_JZ4740_ECC) += jz4740_ecc.o
obj-$(CONFIG_MTD_NAND_JZ4725B_BCH) += jz4725b_bch.o
obj-$(CONFIG_MTD_NAND_JZ4780_BCH) += jz4780_bch.o
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ47xx ECC common code
*
* Copyright (c) 2015 Imagination Technologies
* Author: Alex Smith <alex.smith@imgtec.com>
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "ingenic_ecc.h"
/**
* ingenic_ecc_calculate() - calculate ECC for a data buffer
* @ecc: ECC device.
* @params: ECC parameters.
* @buf: input buffer with raw data.
* @ecc_code: output buffer with ECC.
*
* Return: 0 on success, -ETIMEDOUT if timed out while waiting for ECC
* controller.
*/
int ingenic_ecc_calculate(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code)
{
return ecc->ops->calculate(ecc, params, buf, ecc_code);
}
/**
* ingenic_ecc_correct() - detect and correct bit errors
* @ecc: ECC device.
* @params: ECC parameters.
* @buf: raw data read from the chip.
* @ecc_code: ECC read from the chip.
*
* Given the raw data and the ECC read from the NAND device, detects and
* corrects errors in the data.
*
* Return: the number of bit errors corrected, -EBADMSG if there are too many
* errors to correct or -ETIMEDOUT if we timed out waiting for the controller.
*/
int ingenic_ecc_correct(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
u8 *buf, u8 *ecc_code)
{
return ecc->ops->correct(ecc, params, buf, ecc_code);
}
/**
* ingenic_ecc_get() - get the ECC controller device
* @np: ECC device tree node.
*
* Gets the ECC controller device from the specified device tree node. The
* device must be released with ingenic_ecc_release() when it is no longer being
* used.
*
* Return: a pointer to ingenic_ecc, errors are encoded into the pointer.
* PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
*/
static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np)
{
struct platform_device *pdev;
struct ingenic_ecc *ecc;
int ret;
pdev = of_find_device_by_node(np);
if (!pdev)
return ERR_PTR(-EPROBE_DEFER);
if (!platform_get_drvdata(pdev)) {
put_device(&pdev->dev);
return ERR_PTR(-EPROBE_DEFER);
}
ecc = platform_get_drvdata(pdev);
ret = clk_prepare_enable(ecc->clk);
if (ret) {
put_device(&pdev->dev);
return ERR_PTR(ret);
}
return ecc;
}
/**
* of_ingenic_ecc_get() - get the ECC controller from a DT node
* @of_node: the node that contains an ecc-engine property.
*
* Get the ecc-engine property from the given device tree
* node and pass it to ingenic_ecc_get to do the work.
*
* Return: a pointer to ingenic_ecc, errors are encoded into the pointer.
* PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet.
*/
struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *of_node)
{
struct ingenic_ecc *ecc = NULL;
struct device_node *np;
np = of_parse_phandle(of_node, "ecc-engine", 0);
/*
* If the ecc-engine property is not found, check for the deprecated
* ingenic,bch-controller property
*/
if (!np)
np = of_parse_phandle(of_node, "ingenic,bch-controller", 0);
if (np) {
ecc = ingenic_ecc_get(np);
of_node_put(np);
}
return ecc;
}
/**
* ingenic_ecc_release() - release the ECC controller device
* @ecc: ECC device.
*/
void ingenic_ecc_release(struct ingenic_ecc *ecc)
{
clk_disable_unprepare(ecc->clk);
put_device(ecc->dev);
}
int ingenic_ecc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ingenic_ecc *ecc;
ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
if (!ecc)
return -ENOMEM;
ecc->ops = device_get_match_data(dev);
if (!ecc->ops)
return -EINVAL;
ecc->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ecc->base))
return PTR_ERR(ecc->base);
ecc->ops->disable(ecc);
ecc->clk = devm_clk_get(dev, NULL);
if (IS_ERR(ecc->clk)) {
dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk));
return PTR_ERR(ecc->clk);
}
mutex_init(&ecc->lock);
ecc->dev = dev;
platform_set_drvdata(pdev, ecc);
return 0;
}
EXPORT_SYMBOL(ingenic_ecc_probe);
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__
#define __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__
#include <linux/compiler_types.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <uapi/asm-generic/errno-base.h>
struct clk;
struct device;
struct ingenic_ecc;
struct platform_device;
/**
* struct ingenic_ecc_params - ECC parameters
* @size: data bytes per ECC step.
* @bytes: ECC bytes per step.
* @strength: number of correctable bits per ECC step.
*/
struct ingenic_ecc_params {
int size;
int bytes;
int strength;
};
#if IS_ENABLED(CONFIG_MTD_NAND_INGENIC_ECC)
int ingenic_ecc_calculate(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code);
int ingenic_ecc_correct(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params, u8 *buf,
u8 *ecc_code);
void ingenic_ecc_release(struct ingenic_ecc *ecc);
struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *np);
#else /* CONFIG_MTD_NAND_INGENIC_ECC */
static inline int ingenic_ecc_calculate(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code)
{
return -ENODEV;
}
static inline int ingenic_ecc_correct(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params, u8 *buf,
u8 *ecc_code)
{
return -ENODEV;
}
static inline void ingenic_ecc_release(struct ingenic_ecc *ecc)
{
}
static inline struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *np)
{
return ERR_PTR(-ENODEV);
}
#endif /* CONFIG_MTD_NAND_INGENIC_ECC */
struct ingenic_ecc_ops {
void (*disable)(struct ingenic_ecc *ecc);
int (*calculate)(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code);
int (*correct)(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
u8 *buf, u8 *ecc_code);
};
struct ingenic_ecc {
struct device *dev;
const struct ingenic_ecc_ops *ops;
void __iomem *base;
struct clk *clk;
struct mutex lock;
};
int ingenic_ecc_probe(struct platform_device *pdev);
#endif /* __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__ */
@@ -0,0 +1,561 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Ingenic JZ47xx NAND driver
*
* Copyright (c) 2015 Imagination Technologies
* Author: Alex Smith <alex.smith@imgtec.com>
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/partitions.h>
#include <linux/jz4780-nemc.h>
#include "ingenic_ecc.h"
#define DRV_NAME "ingenic-nand"
struct jz_soc_info {
unsigned long data_offset;
unsigned long addr_offset;
unsigned long cmd_offset;
const struct mtd_ooblayout_ops *oob_layout;
bool oob_first;
};
struct ingenic_nand_cs {
unsigned int bank;
void __iomem *base;
};
struct ingenic_nfc {
struct device *dev;
struct ingenic_ecc *ecc;
const struct jz_soc_info *soc_info;
struct nand_controller controller;
unsigned int num_banks;
struct list_head chips;
struct ingenic_nand_cs cs[] __counted_by(num_banks);
};
struct ingenic_nand {
struct nand_chip chip;
struct list_head chip_list;
struct gpio_desc *busy_gpio;
struct gpio_desc *wp_gpio;
unsigned int reading: 1;
};
static inline struct ingenic_nand *to_ingenic_nand(struct mtd_info *mtd)
{
return container_of(mtd_to_nand(mtd), struct ingenic_nand, chip);
}
static inline struct ingenic_nfc *to_ingenic_nfc(struct nand_controller *ctrl)
{
return container_of(ctrl, struct ingenic_nfc, controller);
}
static int qi_lb60_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
if (section || !ecc->total)
return -ERANGE;
oobregion->length = ecc->total;
oobregion->offset = 12;
return 0;
}
static int qi_lb60_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
if (section)
return -ERANGE;
oobregion->length = mtd->oobsize - ecc->total - 12;
oobregion->offset = 12 + ecc->total;
return 0;
}
static const struct mtd_ooblayout_ops qi_lb60_ooblayout_ops = {
.ecc = qi_lb60_ooblayout_ecc,
.free = qi_lb60_ooblayout_free,
};
static int jz4725b_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
if (section || !ecc->total)
return -ERANGE;
oobregion->length = ecc->total;
oobregion->offset = 3;
return 0;
}
static int jz4725b_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
if (section)
return -ERANGE;
oobregion->length = mtd->oobsize - ecc->total - 3;
oobregion->offset = 3 + ecc->total;
return 0;
}
static const struct mtd_ooblayout_ops jz4725b_ooblayout_ops = {
.ecc = jz4725b_ooblayout_ecc,
.free = jz4725b_ooblayout_free,
};
static void ingenic_nand_ecc_hwctl(struct nand_chip *chip, int mode)
{
struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip));
nand->reading = (mode == NAND_ECC_READ);
}
static int ingenic_nand_ecc_calculate(struct nand_chip *chip, const u8 *dat,
u8 *ecc_code)
{
struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip));
struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller);
struct ingenic_ecc_params params;
/*
* Don't need to generate the ECC when reading, the ECC engine does it
* for us as part of decoding/correction.
*/
if (nand->reading)
return 0;
params.size = nand->chip.ecc.size;
params.bytes = nand->chip.ecc.bytes;
params.strength = nand->chip.ecc.strength;
return ingenic_ecc_calculate(nfc->ecc, &params, dat, ecc_code);
}
static int ingenic_nand_ecc_correct(struct nand_chip *chip, u8 *dat,
u8 *read_ecc, u8 *calc_ecc)
{
struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip));
struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller);
struct ingenic_ecc_params params;
params.size = nand->chip.ecc.size;
params.bytes = nand->chip.ecc.bytes;
params.strength = nand->chip.ecc.strength;
return ingenic_ecc_correct(nfc->ecc, &params, dat, read_ecc);
}
static int ingenic_nand_attach_chip(struct nand_chip *chip)
{
struct mtd_info *mtd = nand_to_mtd(chip);
struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller);
int eccbytes;
if (chip->ecc.strength == 4) {
/* JZ4740 uses 9 bytes of ECC to correct maximum 4 errors */
chip->ecc.bytes = 9;
} else {
chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) *
(chip->ecc.strength / 8);
}
switch (chip->ecc.engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_HOST:
if (!nfc->ecc) {
dev_err(nfc->dev, "HW ECC selected, but ECC controller not found\n");
return -ENODEV;
}
chip->ecc.hwctl = ingenic_nand_ecc_hwctl;
chip->ecc.calculate = ingenic_nand_ecc_calculate;
chip->ecc.correct = ingenic_nand_ecc_correct;
fallthrough;
case NAND_ECC_ENGINE_TYPE_SOFT:
dev_info(nfc->dev, "using %s (strength %d, size %d, bytes %d)\n",
(nfc->ecc) ? "hardware ECC" : "software ECC",
chip->ecc.strength, chip->ecc.size, chip->ecc.bytes);
break;
case NAND_ECC_ENGINE_TYPE_NONE:
dev_info(nfc->dev, "not using ECC\n");
break;
default:
dev_err(nfc->dev, "ECC mode %d not supported\n",
chip->ecc.engine_type);
return -EINVAL;
}
/* The NAND core will generate the ECC layout for SW ECC */
if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
return 0;
/* Generate ECC layout. ECC codes are right aligned in the OOB area. */
eccbytes = mtd->writesize / chip->ecc.size * chip->ecc.bytes;
if (eccbytes > mtd->oobsize - 2) {
dev_err(nfc->dev,
"invalid ECC config: required %d ECC bytes, but only %d are available",
eccbytes, mtd->oobsize - 2);
return -EINVAL;
}
/*
* The generic layout for BBT markers will most likely overlap with our
* ECC bytes in the OOB, so move the BBT markers outside the OOB area.
*/
if (chip->bbt_options & NAND_BBT_USE_FLASH)
chip->bbt_options |= NAND_BBT_NO_OOB;
if (nfc->soc_info->oob_first)
chip->ecc.read_page = nand_read_page_hwecc_oob_first;
/* For legacy reasons we use a different layout on the qi,lb60 board. */
if (of_machine_is_compatible("qi,lb60"))
mtd_set_ooblayout(mtd, &qi_lb60_ooblayout_ops);
else if (nfc->soc_info->oob_layout)
mtd_set_ooblayout(mtd, nfc->soc_info->oob_layout);
else
mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
return 0;
}
static int ingenic_nand_exec_instr(struct nand_chip *chip,
struct ingenic_nand_cs *cs,
const struct nand_op_instr *instr)
{
struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip));
struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller);
unsigned int i;
switch (instr->type) {
case NAND_OP_CMD_INSTR:
writeb(instr->ctx.cmd.opcode,
cs->base + nfc->soc_info->cmd_offset);
return 0;
case NAND_OP_ADDR_INSTR:
for (i = 0; i < instr->ctx.addr.naddrs; i++)
writeb(instr->ctx.addr.addrs[i],
cs->base + nfc->soc_info->addr_offset);
return 0;
case NAND_OP_DATA_IN_INSTR:
if (instr->ctx.data.force_8bit ||
!(chip->options & NAND_BUSWIDTH_16))
ioread8_rep(cs->base + nfc->soc_info->data_offset,
instr->ctx.data.buf.in,
instr->ctx.data.len);
else
ioread16_rep(cs->base + nfc->soc_info->data_offset,
instr->ctx.data.buf.in,
instr->ctx.data.len);
return 0;
case NAND_OP_DATA_OUT_INSTR:
if (instr->ctx.data.force_8bit ||
!(chip->options & NAND_BUSWIDTH_16))
iowrite8_rep(cs->base + nfc->soc_info->data_offset,
instr->ctx.data.buf.out,
instr->ctx.data.len);
else
iowrite16_rep(cs->base + nfc->soc_info->data_offset,
instr->ctx.data.buf.out,
instr->ctx.data.len);
return 0;
case NAND_OP_WAITRDY_INSTR:
if (!nand->busy_gpio)
return nand_soft_waitrdy(chip,
instr->ctx.waitrdy.timeout_ms);
return nand_gpio_waitrdy(chip, nand->busy_gpio,
instr->ctx.waitrdy.timeout_ms);
default:
break;
}
return -EINVAL;
}
static int ingenic_nand_exec_op(struct nand_chip *chip,
const struct nand_operation *op,
bool check_only)
{
struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip));
struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller);
struct ingenic_nand_cs *cs;
unsigned int i;
int ret = 0;
if (check_only)
return 0;
cs = &nfc->cs[op->cs];
jz4780_nemc_assert(nfc->dev, cs->bank, true);
for (i = 0; i < op->ninstrs; i++) {
ret = ingenic_nand_exec_instr(chip, cs, &op->instrs[i]);
if (ret)
break;
if (op->instrs[i].delay_ns)
ndelay(op->instrs[i].delay_ns);
}
jz4780_nemc_assert(nfc->dev, cs->bank, false);
return ret;
}
static const struct nand_controller_ops ingenic_nand_controller_ops = {
.attach_chip = ingenic_nand_attach_chip,
.exec_op = ingenic_nand_exec_op,
};
static int ingenic_nand_init_chip(struct platform_device *pdev,
struct ingenic_nfc *nfc,
struct device_node *np,
unsigned int chipnr)
{
struct device *dev = &pdev->dev;
struct ingenic_nand *nand;
struct ingenic_nand_cs *cs;
struct nand_chip *chip;
struct mtd_info *mtd;
const __be32 *reg;
int ret = 0;
cs = &nfc->cs[chipnr];
reg = of_get_property(np, "reg", NULL);
if (!reg)
return -EINVAL;
cs->bank = be32_to_cpu(*reg);
jz4780_nemc_set_type(nfc->dev, cs->bank, JZ4780_NEMC_BANK_NAND);
cs->base = devm_platform_ioremap_resource(pdev, chipnr);
if (IS_ERR(cs->base))
return PTR_ERR(cs->base);
nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
if (!nand)
return -ENOMEM;
nand->busy_gpio = devm_gpiod_get_optional(dev, "rb", GPIOD_IN);
if (IS_ERR(nand->busy_gpio)) {
ret = PTR_ERR(nand->busy_gpio);
dev_err(dev, "failed to request busy GPIO: %d\n", ret);
return ret;
}
nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW);
if (IS_ERR(nand->wp_gpio)) {
ret = PTR_ERR(nand->wp_gpio);
dev_err(dev, "failed to request WP GPIO: %d\n", ret);
return ret;
}
chip = &nand->chip;
mtd = nand_to_mtd(chip);
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev),
cs->bank);
if (!mtd->name)
return -ENOMEM;
mtd->dev.parent = dev;
chip->options = NAND_NO_SUBPAGE_WRITE;
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
chip->controller = &nfc->controller;
nand_set_flash_node(chip, np);
chip->controller->ops = &ingenic_nand_controller_ops;
ret = nand_scan(chip, 1);
if (ret)
return ret;
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
nand_cleanup(chip);
return ret;
}
list_add_tail(&nand->chip_list, &nfc->chips);
return 0;
}
static void ingenic_nand_cleanup_chips(struct ingenic_nfc *nfc)
{
struct ingenic_nand *ingenic_chip;
struct nand_chip *chip;
int ret;
while (!list_empty(&nfc->chips)) {
ingenic_chip = list_first_entry(&nfc->chips,
struct ingenic_nand, chip_list);
chip = &ingenic_chip->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
nand_cleanup(chip);
list_del(&ingenic_chip->chip_list);
}
}
static int ingenic_nand_init_chips(struct ingenic_nfc *nfc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int i = 0;
int ret;
int num_chips = of_get_child_count(dev->of_node);
if (num_chips > nfc->num_banks) {
dev_err(dev, "found %d chips but only %d banks\n",
num_chips, nfc->num_banks);
return -EINVAL;
}
for_each_child_of_node_scoped(dev->of_node, np) {
ret = ingenic_nand_init_chip(pdev, nfc, np, i);
if (ret) {
ingenic_nand_cleanup_chips(nfc);
return ret;
}
i++;
}
return 0;
}
static int ingenic_nand_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
unsigned int num_banks;
struct ingenic_nfc *nfc;
int ret;
num_banks = jz4780_nemc_num_banks(dev);
if (num_banks == 0) {
dev_err(dev, "no banks found\n");
return -ENODEV;
}
nfc = devm_kzalloc(dev, struct_size(nfc, cs, num_banks), GFP_KERNEL);
if (!nfc)
return -ENOMEM;
nfc->soc_info = device_get_match_data(dev);
if (!nfc->soc_info)
return -EINVAL;
/*
* Check for ECC HW before we call nand_scan_ident, to prevent us from
* having to call it again if the ECC driver returns -EPROBE_DEFER.
*/
nfc->ecc = of_ingenic_ecc_get(dev->of_node);
if (IS_ERR(nfc->ecc))
return PTR_ERR(nfc->ecc);
nfc->dev = dev;
nfc->num_banks = num_banks;
nand_controller_init(&nfc->controller);
INIT_LIST_HEAD(&nfc->chips);
ret = ingenic_nand_init_chips(nfc, pdev);
if (ret) {
if (nfc->ecc)
ingenic_ecc_release(nfc->ecc);
return ret;
}
platform_set_drvdata(pdev, nfc);
return 0;
}
static void ingenic_nand_remove(struct platform_device *pdev)
{
struct ingenic_nfc *nfc = platform_get_drvdata(pdev);
if (nfc->ecc)
ingenic_ecc_release(nfc->ecc);
ingenic_nand_cleanup_chips(nfc);
}
static const struct jz_soc_info jz4740_soc_info = {
.data_offset = 0x00000000,
.cmd_offset = 0x00008000,
.addr_offset = 0x00010000,
.oob_first = true,
};
static const struct jz_soc_info jz4725b_soc_info = {
.data_offset = 0x00000000,
.cmd_offset = 0x00008000,
.addr_offset = 0x00010000,
.oob_layout = &jz4725b_ooblayout_ops,
};
static const struct jz_soc_info jz4780_soc_info = {
.data_offset = 0x00000000,
.cmd_offset = 0x00400000,
.addr_offset = 0x00800000,
};
static const struct of_device_id ingenic_nand_dt_match[] = {
{ .compatible = "ingenic,jz4740-nand", .data = &jz4740_soc_info },
{ .compatible = "ingenic,jz4725b-nand", .data = &jz4725b_soc_info },
{ .compatible = "ingenic,jz4780-nand", .data = &jz4780_soc_info },
{},
};
MODULE_DEVICE_TABLE(of, ingenic_nand_dt_match);
static struct platform_driver ingenic_nand_driver = {
.probe = ingenic_nand_probe,
.remove = ingenic_nand_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = ingenic_nand_dt_match,
},
};
module_platform_driver(ingenic_nand_driver);
MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
MODULE_DESCRIPTION("Ingenic JZ47xx NAND driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,295 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ4725B BCH controller driver
*
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
*
* Based on jz4780_bch.c
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "ingenic_ecc.h"
#define BCH_BHCR 0x0
#define BCH_BHCSR 0x4
#define BCH_BHCCR 0x8
#define BCH_BHCNT 0xc
#define BCH_BHDR 0x10
#define BCH_BHPAR0 0x14
#define BCH_BHERR0 0x28
#define BCH_BHINT 0x24
#define BCH_BHINTES 0x3c
#define BCH_BHINTEC 0x40
#define BCH_BHINTE 0x38
#define BCH_BHCR_ENCE BIT(3)
#define BCH_BHCR_BSEL BIT(2)
#define BCH_BHCR_INIT BIT(1)
#define BCH_BHCR_BCHE BIT(0)
#define BCH_BHCNT_DEC_COUNT_SHIFT 16
#define BCH_BHCNT_DEC_COUNT_MASK (0x3ff << BCH_BHCNT_DEC_COUNT_SHIFT)
#define BCH_BHCNT_ENC_COUNT_SHIFT 0
#define BCH_BHCNT_ENC_COUNT_MASK (0x3ff << BCH_BHCNT_ENC_COUNT_SHIFT)
#define BCH_BHERR_INDEX0_SHIFT 0
#define BCH_BHERR_INDEX0_MASK (0x1fff << BCH_BHERR_INDEX0_SHIFT)
#define BCH_BHERR_INDEX1_SHIFT 16
#define BCH_BHERR_INDEX1_MASK (0x1fff << BCH_BHERR_INDEX1_SHIFT)
#define BCH_BHINT_ERRC_SHIFT 28
#define BCH_BHINT_ERRC_MASK (0xf << BCH_BHINT_ERRC_SHIFT)
#define BCH_BHINT_TERRC_SHIFT 16
#define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT)
#define BCH_BHINT_ALL_0 BIT(5)
#define BCH_BHINT_ALL_F BIT(4)
#define BCH_BHINT_DECF BIT(3)
#define BCH_BHINT_ENCF BIT(2)
#define BCH_BHINT_UNCOR BIT(1)
#define BCH_BHINT_ERR BIT(0)
/* Timeout for BCH calculation/correction. */
#define BCH_TIMEOUT_US 100000
static inline void jz4725b_bch_config_set(struct ingenic_ecc *bch, u32 cfg)
{
writel(cfg, bch->base + BCH_BHCSR);
}
static inline void jz4725b_bch_config_clear(struct ingenic_ecc *bch, u32 cfg)
{
writel(cfg, bch->base + BCH_BHCCR);
}
static int jz4725b_bch_reset(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params, bool calc_ecc)
{
u32 reg, max_value;
/* Clear interrupt status. */
writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
/* Initialise and enable BCH. */
jz4725b_bch_config_clear(bch, 0x1f);
jz4725b_bch_config_set(bch, BCH_BHCR_BCHE);
if (params->strength == 8)
jz4725b_bch_config_set(bch, BCH_BHCR_BSEL);
else
jz4725b_bch_config_clear(bch, BCH_BHCR_BSEL);
if (calc_ecc) /* calculate ECC from data */
jz4725b_bch_config_set(bch, BCH_BHCR_ENCE);
else /* correct data from ECC */
jz4725b_bch_config_clear(bch, BCH_BHCR_ENCE);
jz4725b_bch_config_set(bch, BCH_BHCR_INIT);
max_value = BCH_BHCNT_ENC_COUNT_MASK >> BCH_BHCNT_ENC_COUNT_SHIFT;
if (params->size > max_value)
return -EINVAL;
max_value = BCH_BHCNT_DEC_COUNT_MASK >> BCH_BHCNT_DEC_COUNT_SHIFT;
if (params->size + params->bytes > max_value)
return -EINVAL;
/* Set up BCH count register. */
reg = params->size << BCH_BHCNT_ENC_COUNT_SHIFT;
reg |= (params->size + params->bytes) << BCH_BHCNT_DEC_COUNT_SHIFT;
writel(reg, bch->base + BCH_BHCNT);
return 0;
}
static void jz4725b_bch_disable(struct ingenic_ecc *bch)
{
/* Clear interrupts */
writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
/* Disable the hardware */
jz4725b_bch_config_clear(bch, BCH_BHCR_BCHE);
}
static void jz4725b_bch_write_data(struct ingenic_ecc *bch, const u8 *buf,
size_t size)
{
while (size--)
writeb(*buf++, bch->base + BCH_BHDR);
}
static void jz4725b_bch_read_parity(struct ingenic_ecc *bch, u8 *buf,
size_t size)
{
size_t size32 = size / sizeof(u32);
size_t size8 = size % sizeof(u32);
u32 *dest32;
u8 *dest8;
u32 val, offset = 0;
dest32 = (u32 *)buf;
while (size32--) {
*dest32++ = readl_relaxed(bch->base + BCH_BHPAR0 + offset);
offset += sizeof(u32);
}
dest8 = (u8 *)dest32;
val = readl_relaxed(bch->base + BCH_BHPAR0 + offset);
switch (size8) {
case 3:
dest8[2] = (val >> 16) & 0xff;
fallthrough;
case 2:
dest8[1] = (val >> 8) & 0xff;
fallthrough;
case 1:
dest8[0] = val & 0xff;
break;
}
}
static int jz4725b_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq,
u32 *status)
{
u32 reg;
int ret;
/*
* While we could use interrupts here and sleep until the operation
* completes, the controller works fairly quickly (usually a few
* microseconds) and so the overhead of sleeping until we get an
* interrupt quite noticeably decreases performance.
*/
ret = readl_relaxed_poll_timeout(bch->base + BCH_BHINT, reg,
reg & irq, 0, BCH_TIMEOUT_US);
if (ret)
return ret;
if (status)
*status = reg;
writel(reg, bch->base + BCH_BHINT);
return 0;
}
static int jz4725b_calculate(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code)
{
int ret;
mutex_lock(&bch->lock);
ret = jz4725b_bch_reset(bch, params, true);
if (ret) {
dev_err(bch->dev, "Unable to init BCH with given parameters\n");
goto out_disable;
}
jz4725b_bch_write_data(bch, buf, params->size);
ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL);
if (ret) {
dev_err(bch->dev, "timed out while calculating ECC\n");
goto out_disable;
}
jz4725b_bch_read_parity(bch, ecc_code, params->bytes);
out_disable:
jz4725b_bch_disable(bch);
mutex_unlock(&bch->lock);
return ret;
}
static int jz4725b_correct(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params,
u8 *buf, u8 *ecc_code)
{
u32 reg, errors, bit;
unsigned int i;
int ret;
mutex_lock(&bch->lock);
ret = jz4725b_bch_reset(bch, params, false);
if (ret) {
dev_err(bch->dev, "Unable to init BCH with given parameters\n");
goto out;
}
jz4725b_bch_write_data(bch, buf, params->size);
jz4725b_bch_write_data(bch, ecc_code, params->bytes);
ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_DECF, &reg);
if (ret) {
dev_err(bch->dev, "timed out while correcting data\n");
goto out;
}
if (reg & (BCH_BHINT_ALL_F | BCH_BHINT_ALL_0)) {
/* Data and ECC is all 0xff or 0x00 - nothing to correct */
ret = 0;
goto out;
}
if (reg & BCH_BHINT_UNCOR) {
/* Uncorrectable ECC error */
ret = -EBADMSG;
goto out;
}
errors = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;
/* Correct any detected errors. */
for (i = 0; i < errors; i++) {
if (i & 1) {
bit = (reg & BCH_BHERR_INDEX1_MASK) >> BCH_BHERR_INDEX1_SHIFT;
} else {
reg = readl(bch->base + BCH_BHERR0 + (i * 4));
bit = (reg & BCH_BHERR_INDEX0_MASK) >> BCH_BHERR_INDEX0_SHIFT;
}
buf[(bit >> 3)] ^= BIT(bit & 0x7);
}
out:
jz4725b_bch_disable(bch);
mutex_unlock(&bch->lock);
return ret;
}
static const struct ingenic_ecc_ops jz4725b_bch_ops = {
.disable = jz4725b_bch_disable,
.calculate = jz4725b_calculate,
.correct = jz4725b_correct,
};
static const struct of_device_id jz4725b_bch_dt_match[] = {
{ .compatible = "ingenic,jz4725b-bch", .data = &jz4725b_bch_ops },
{},
};
MODULE_DEVICE_TABLE(of, jz4725b_bch_dt_match);
static struct platform_driver jz4725b_bch_driver = {
.probe = ingenic_ecc_probe,
.driver = {
.name = "jz4725b-bch",
.of_match_table = jz4725b_bch_dt_match,
},
};
module_platform_driver(jz4725b_bch_driver);
MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ4740 ECC controller driver
*
* Copyright (c) 2019 Paul Cercueil <paul@crapouillou.net>
*
* based on jz4740-nand.c
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "ingenic_ecc.h"
#define JZ_REG_NAND_ECC_CTRL 0x00
#define JZ_REG_NAND_DATA 0x04
#define JZ_REG_NAND_PAR0 0x08
#define JZ_REG_NAND_PAR1 0x0C
#define JZ_REG_NAND_PAR2 0x10
#define JZ_REG_NAND_IRQ_STAT 0x14
#define JZ_REG_NAND_IRQ_CTRL 0x18
#define JZ_REG_NAND_ERR(x) (0x1C + ((x) << 2))
#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
#define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
#define JZ_NAND_ECC_CTRL_RS BIT(2)
#define JZ_NAND_ECC_CTRL_RESET BIT(1)
#define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
#define JZ_NAND_STATUS_PAD_FINISH BIT(4)
#define JZ_NAND_STATUS_DEC_FINISH BIT(3)
#define JZ_NAND_STATUS_ENC_FINISH BIT(2)
#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
#define JZ_NAND_STATUS_ERROR BIT(0)
static const uint8_t empty_block_ecc[] = {
0xcd, 0x9d, 0x90, 0x58, 0xf4, 0x8b, 0xff, 0xb7, 0x6f
};
static void jz4740_ecc_reset(struct ingenic_ecc *ecc, bool calc_ecc)
{
uint32_t reg;
/* Clear interrupt status */
writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT);
/* Initialize and enable ECC hardware */
reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL);
reg |= JZ_NAND_ECC_CTRL_RESET;
reg |= JZ_NAND_ECC_CTRL_ENABLE;
reg |= JZ_NAND_ECC_CTRL_RS;
if (calc_ecc) /* calculate ECC from data */
reg |= JZ_NAND_ECC_CTRL_ENCODING;
else /* correct data from ECC */
reg &= ~JZ_NAND_ECC_CTRL_ENCODING;
writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL);
}
static int jz4740_ecc_calculate(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code)
{
uint32_t reg, status;
unsigned int timeout = 1000;
int i;
jz4740_ecc_reset(ecc, true);
do {
status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT);
} while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout);
if (timeout == 0)
return -ETIMEDOUT;
reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL);
reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL);
for (i = 0; i < params->bytes; ++i)
ecc_code[i] = readb(ecc->base + JZ_REG_NAND_PAR0 + i);
/*
* If the written data is completely 0xff, we also want to write 0xff as
* ECC, otherwise we will get in trouble when doing subpage writes.
*/
if (memcmp(ecc_code, empty_block_ecc, sizeof(empty_block_ecc)) == 0)
memset(ecc_code, 0xff, sizeof(empty_block_ecc));
return 0;
}
static void jz_nand_correct_data(uint8_t *buf, int index, int mask)
{
int offset = index & 0x7;
uint16_t data;
index += (index >> 3);
data = buf[index];
data |= buf[index + 1] << 8;
mask ^= (data >> offset) & 0x1ff;
data &= ~(0x1ff << offset);
data |= (mask << offset);
buf[index] = data & 0xff;
buf[index + 1] = (data >> 8) & 0xff;
}
static int jz4740_ecc_correct(struct ingenic_ecc *ecc,
struct ingenic_ecc_params *params,
u8 *buf, u8 *ecc_code)
{
int i, error_count, index;
uint32_t reg, status, error;
unsigned int timeout = 1000;
jz4740_ecc_reset(ecc, false);
for (i = 0; i < params->bytes; ++i)
writeb(ecc_code[i], ecc->base + JZ_REG_NAND_PAR0 + i);
reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL);
reg |= JZ_NAND_ECC_CTRL_PAR_READY;
writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL);
do {
status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT);
} while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout);
if (timeout == 0)
return -ETIMEDOUT;
reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL);
reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL);
if (status & JZ_NAND_STATUS_ERROR) {
if (status & JZ_NAND_STATUS_UNCOR_ERROR)
return -EBADMSG;
error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29;
for (i = 0; i < error_count; ++i) {
error = readl(ecc->base + JZ_REG_NAND_ERR(i));
index = ((error >> 16) & 0x1ff) - 1;
if (index >= 0 && index < params->size)
jz_nand_correct_data(buf, index, error & 0x1ff);
}
return error_count;
}
return 0;
}
static void jz4740_ecc_disable(struct ingenic_ecc *ecc)
{
u32 reg;
writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT);
reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL);
reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL);
}
static const struct ingenic_ecc_ops jz4740_ecc_ops = {
.disable = jz4740_ecc_disable,
.calculate = jz4740_ecc_calculate,
.correct = jz4740_ecc_correct,
};
static const struct of_device_id jz4740_ecc_dt_match[] = {
{ .compatible = "ingenic,jz4740-ecc", .data = &jz4740_ecc_ops },
{},
};
MODULE_DEVICE_TABLE(of, jz4740_ecc_dt_match);
static struct platform_driver jz4740_ecc_driver = {
.probe = ingenic_ecc_probe,
.driver = {
.name = "jz4740-ecc",
.of_match_table = jz4740_ecc_dt_match,
},
};
module_platform_driver(jz4740_ecc_driver);
MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
MODULE_DESCRIPTION("Ingenic JZ4740 ECC controller driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ4780 BCH controller driver
*
* Copyright (c) 2015 Imagination Technologies
* Author: Alex Smith <alex.smith@imgtec.com>
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "ingenic_ecc.h"
#define BCH_BHCR 0x0
#define BCH_BHCCR 0x8
#define BCH_BHCNT 0xc
#define BCH_BHDR 0x10
#define BCH_BHPAR0 0x14
#define BCH_BHERR0 0x84
#define BCH_BHINT 0x184
#define BCH_BHINTES 0x188
#define BCH_BHINTEC 0x18c
#define BCH_BHINTE 0x190
#define BCH_BHCR_BSEL_SHIFT 4
#define BCH_BHCR_BSEL_MASK (0x7f << BCH_BHCR_BSEL_SHIFT)
#define BCH_BHCR_ENCE BIT(2)
#define BCH_BHCR_INIT BIT(1)
#define BCH_BHCR_BCHE BIT(0)
#define BCH_BHCNT_PARITYSIZE_SHIFT 16
#define BCH_BHCNT_PARITYSIZE_MASK (0x7f << BCH_BHCNT_PARITYSIZE_SHIFT)
#define BCH_BHCNT_BLOCKSIZE_SHIFT 0
#define BCH_BHCNT_BLOCKSIZE_MASK (0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT)
#define BCH_BHERR_MASK_SHIFT 16
#define BCH_BHERR_MASK_MASK (0xffff << BCH_BHERR_MASK_SHIFT)
#define BCH_BHERR_INDEX_SHIFT 0
#define BCH_BHERR_INDEX_MASK (0x7ff << BCH_BHERR_INDEX_SHIFT)
#define BCH_BHINT_ERRC_SHIFT 24
#define BCH_BHINT_ERRC_MASK (0x7f << BCH_BHINT_ERRC_SHIFT)
#define BCH_BHINT_TERRC_SHIFT 16
#define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT)
#define BCH_BHINT_DECF BIT(3)
#define BCH_BHINT_ENCF BIT(2)
#define BCH_BHINT_UNCOR BIT(1)
#define BCH_BHINT_ERR BIT(0)
#define BCH_CLK_RATE (200 * 1000 * 1000)
/* Timeout for BCH calculation/correction. */
#define BCH_TIMEOUT_US 100000
static void jz4780_bch_reset(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params, bool encode)
{
u32 reg;
/* Clear interrupt status. */
writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
/* Set up BCH count register. */
reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT;
reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT;
writel(reg, bch->base + BCH_BHCNT);
/* Initialise and enable BCH. */
reg = BCH_BHCR_BCHE | BCH_BHCR_INIT;
reg |= params->strength << BCH_BHCR_BSEL_SHIFT;
if (encode)
reg |= BCH_BHCR_ENCE;
writel(reg, bch->base + BCH_BHCR);
}
static void jz4780_bch_disable(struct ingenic_ecc *bch)
{
writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR);
}
static void jz4780_bch_write_data(struct ingenic_ecc *bch, const void *buf,
size_t size)
{
size_t size32 = size / sizeof(u32);
size_t size8 = size % sizeof(u32);
const u32 *src32;
const u8 *src8;
src32 = (const u32 *)buf;
while (size32--)
writel(*src32++, bch->base + BCH_BHDR);
src8 = (const u8 *)src32;
while (size8--)
writeb(*src8++, bch->base + BCH_BHDR);
}
static void jz4780_bch_read_parity(struct ingenic_ecc *bch, void *buf,
size_t size)
{
size_t size32 = size / sizeof(u32);
size_t size8 = size % sizeof(u32);
u32 *dest32;
u8 *dest8;
u32 val, offset = 0;
dest32 = (u32 *)buf;
while (size32--) {
*dest32++ = readl(bch->base + BCH_BHPAR0 + offset);
offset += sizeof(u32);
}
dest8 = (u8 *)dest32;
val = readl(bch->base + BCH_BHPAR0 + offset);
switch (size8) {
case 3:
dest8[2] = (val >> 16) & 0xff;
fallthrough;
case 2:
dest8[1] = (val >> 8) & 0xff;
fallthrough;
case 1:
dest8[0] = val & 0xff;
break;
}
}
static bool jz4780_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq,
u32 *status)
{
u32 reg;
int ret;
/*
* While we could use interrupts here and sleep until the operation
* completes, the controller works fairly quickly (usually a few
* microseconds) and so the overhead of sleeping until we get an
* interrupt quite noticeably decreases performance.
*/
ret = readl_poll_timeout(bch->base + BCH_BHINT, reg,
(reg & irq) == irq, 0, BCH_TIMEOUT_US);
if (ret)
return false;
if (status)
*status = reg;
writel(reg, bch->base + BCH_BHINT);
return true;
}
static int jz4780_calculate(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params,
const u8 *buf, u8 *ecc_code)
{
int ret = 0;
mutex_lock(&bch->lock);
jz4780_bch_reset(bch, params, true);
jz4780_bch_write_data(bch, buf, params->size);
if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) {
jz4780_bch_read_parity(bch, ecc_code, params->bytes);
} else {
dev_err(bch->dev, "timed out while calculating ECC\n");
ret = -ETIMEDOUT;
}
jz4780_bch_disable(bch);
mutex_unlock(&bch->lock);
return ret;
}
static int jz4780_correct(struct ingenic_ecc *bch,
struct ingenic_ecc_params *params,
u8 *buf, u8 *ecc_code)
{
u32 reg, mask, index;
int i, ret, count;
mutex_lock(&bch->lock);
jz4780_bch_reset(bch, params, false);
jz4780_bch_write_data(bch, buf, params->size);
jz4780_bch_write_data(bch, ecc_code, params->bytes);
if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, &reg)) {
dev_err(bch->dev, "timed out while correcting data\n");
ret = -ETIMEDOUT;
goto out;
}
if (reg & BCH_BHINT_UNCOR) {
dev_warn(bch->dev, "uncorrectable ECC error\n");
ret = -EBADMSG;
goto out;
}
/* Correct any detected errors. */
if (reg & BCH_BHINT_ERR) {
count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;
ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT;
for (i = 0; i < count; i++) {
reg = readl(bch->base + BCH_BHERR0 + (i * 4));
mask = (reg & BCH_BHERR_MASK_MASK) >>
BCH_BHERR_MASK_SHIFT;
index = (reg & BCH_BHERR_INDEX_MASK) >>
BCH_BHERR_INDEX_SHIFT;
buf[(index * 2) + 0] ^= mask;
buf[(index * 2) + 1] ^= mask >> 8;
}
} else {
ret = 0;
}
out:
jz4780_bch_disable(bch);
mutex_unlock(&bch->lock);
return ret;
}
static int jz4780_bch_probe(struct platform_device *pdev)
{
struct ingenic_ecc *bch;
int ret;
ret = ingenic_ecc_probe(pdev);
if (ret)
return ret;
bch = platform_get_drvdata(pdev);
clk_set_rate(bch->clk, BCH_CLK_RATE);
return 0;
}
static const struct ingenic_ecc_ops jz4780_bch_ops = {
.disable = jz4780_bch_disable,
.calculate = jz4780_calculate,
.correct = jz4780_correct,
};
static const struct of_device_id jz4780_bch_dt_match[] = {
{ .compatible = "ingenic,jz4780-bch", .data = &jz4780_bch_ops },
{},
};
MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match);
static struct platform_driver jz4780_bch_driver = {
.probe = jz4780_bch_probe,
.driver = {
.name = "jz4780-bch",
.of_match_table = jz4780_bch_dt_match,
},
};
module_platform_driver(jz4780_bch_driver);
MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");
MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");
MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver");
MODULE_LICENSE("GPL v2");