[RFC preliminary v0.1] nfc driver for tango platform
Mason
slash.tmp at free.fr
Tue Aug 30 05:55:46 PDT 2016
Hello linux-mtd,
I've been writing a driver for the custom NAND Flash controller embedded
in tango chips, such as smp8758.
I'd like to hear every and all feedback on what I did wrong so that I can
eventually submit the work upstream. Please bear in mind that this is a
preliminary version, so there are still a few rough edges.
Regards.
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mtd/nand.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
struct tango_nfc {
struct nand_hw_control hw;
void __iomem *reg_base, *mem_base, *pbus_base;
struct tango_chip *chips[4];
struct dma_chan *chan;
};
#define to_tango_nfc(ptr) container_of(ptr, struct tango_nfc, hw)
struct tango_chip {
struct nand_chip chip;
void __iomem *base;
int cs;
};
#define to_tango_chip(ptr) container_of(ptr, struct tango_chip, chip)
#define PBUS_PAD_MODE 0x8f0
#define MODE_RAW 0
#define MODE_MLC BIT(31)
#define CMD_OFFSET 0
#define ADDR_OFFSET 4
#define DATA_OFFSET 8
#define NFC_STATUS_REG 0x00
#define NFC_FLASH_CMD 0x04
#define NFC_DEVICE_CFG 0x08
#define NFC_TIMING1 0x0c
#define NFC_TIMING2 0x10
#define NFC_XFER_CFG 0x14
#define NFC_PKT_0_CFG 0x18
#define NFC_PKT_N_CFG 0x1c
#define NFC_BB_CFG 0x20
#define NFC_ADDR_PAGE 0x24
#define NFC_ADDR_OFFSET 0x28
#define NFC_XFER_STATUS 0x2c
static void tango_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
struct tango_chip *chip = to_tango_chip(mtd_to_nand(mtd));
if (ctrl & NAND_CLE)
writeb(dat, chip->base + CMD_OFFSET);
if (ctrl & NAND_ALE)
writeb(dat, chip->base + ADDR_OFFSET);
}
static int tango_dev_ready(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct tango_nfc *nfc = to_tango_nfc(chip->controller);
return readl_relaxed(nfc->pbus_base + 0x83c) & BIT(31);
}
static uint8_t tango_read_byte(struct mtd_info *mtd)
{
struct tango_chip *chip = to_tango_chip(mtd_to_nand(mtd));
return readb_relaxed(chip->base + DATA_OFFSET);
}
static void tango_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
{
struct tango_chip *chip = to_tango_chip(mtd_to_nand(mtd));
ioread8_rep(chip->base + DATA_OFFSET, buf, len);
}
static int decode_error_report(struct tango_nfc *nfc)
{
u32 xfer_status, report, pkt0, pktn;
xfer_status = readl_relaxed(nfc->reg_base + NFC_XFER_STATUS);
if (xfer_status & BIT(16)) /* page is empty */
return 0;
report = readl_relaxed(nfc->mem_base + 0x1c0);
if ((report & BIT(7)) == 0) /* too many bitflips on first packet */
return -EBADMSG;
if ((report & BIT(15)) == 0) /* too many bitflips on another packet */
return -EBADMSG;
pkt0 = (report >> 0) & 63;
pktn = (report >> 8) & 63;
return max(pkt0, pktn);
}
static void tango_dma_callback(void *arg)
{
complete(arg);
}
static int do_dma(struct mtd_info *mtd, void *addr, unsigned int len, int page,
void __iomem *reg_base, struct dma_chan *chan, int dir)
{
struct tango_chip *chip = to_tango_chip(mtd_to_nand(mtd));
struct dma_async_tx_descriptor *desc;
struct scatterlist sg;
struct completion tx_done;
int err = -EIO;
sg_init_one(&sg, addr, len);
if (dma_map_sg(chan->device->dev, &sg, 1, dir) != 1)
goto L1;
desc = dmaengine_prep_slave_sg(chan, &sg, 1, dir, DMA_PREP_INTERRUPT);
if (!desc)
goto L2;
desc->callback = tango_dma_callback;
desc->callback_param = &tx_done;
init_completion(&tx_done);
dmaengine_submit(desc);
dma_async_issue_pending(chan);
err = 0;
writel(chip->cs << 24 | 0x00010204, reg_base + NFC_XFER_CFG);
writel(page, reg_base + NFC_ADDR_PAGE);
writel(0, reg_base + NFC_ADDR_OFFSET);
writel(1, reg_base + NFC_FLASH_CMD);
wait_for_completion(&tx_done);
while (readl(reg_base + NFC_STATUS_REG) & 0xf)
cpu_relax();
L2:
dma_unmap_sg(chan->device->dev, &sg, 1, dir);
L1:
return err;
}
static int tango_read_page(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int oob_required, int page)
{
int ret, err, len = mtd->writesize;
struct tango_nfc *nfc = to_tango_nfc(chip->controller);
//printk("%s: buf=%p oob_req=%d page=%d\n", __func__, buf, oob_required, page);
writel(MODE_MLC, nfc->pbus_base + PBUS_PAD_MODE);
err = do_dma(mtd, buf, len, page, nfc->reg_base, nfc->chan, DMA_FROM_DEVICE);
ret = err ? err : decode_error_report(nfc);
writel(MODE_RAW, nfc->pbus_base + PBUS_PAD_MODE);
return ret;
}
static int tango_write_page(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int oob_required, int page)
{
int ret, err, len = mtd->writesize;
struct tango_nfc *nfc = to_tango_nfc(chip->controller);
//printk("%s: buf=%p oob_req=%d page=%d\n", __func__, buf, oob_required, page);
writel(MODE_MLC, nfc->pbus_base + PBUS_PAD_MODE);
err = do_dma(mtd, (void *)buf, len, page, nfc->reg_base, nfc->chan, DMA_TO_DEVICE);
ret = err ? err : decode_error_report(nfc);
writel(MODE_RAW, nfc->pbus_base + PBUS_PAD_MODE);
return ret;
}
static int chip_init(struct device *dev, struct tango_nfc *nfc, int cs)
{
int err;
struct tango_chip *p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->chip.read_byte = tango_read_byte;
p->chip.read_buf = tango_read_buf;
p->chip.cmd_ctrl = tango_cmd_ctrl;
p->chip.dev_ready = tango_dev_ready;
//p->chip.options = NAND_SKIP_BBTSCAN;
p->chip.controller = &nfc->hw;
p->chip.ecc.mode = NAND_ECC_HW;
p->chip.ecc.algo = NAND_ECC_BCH;
p->chip.ecc.steps = 2;
p->chip.ecc.size = 1024;
p->chip.ecc.bytes = 27;
p->chip.ecc.strength = 14;
p->chip.ecc.read_page = tango_read_page;
p->chip.ecc.write_page = tango_write_page;
p->base = nfc->pbus_base + (cs * 256);
p->cs = cs;
err = nand_scan(&p->chip.mtd, 0);
printk("nand_scan=%d\n", err);
if (err) return err;
err = mtd_device_register(&p->chip.mtd, NULL, 0);
printk("mtd_device_register=%d\n", err);
nfc->chips[cs] = p;
return err;
}
static int tango_nand_probe(struct platform_device *pdev)
{
int i;
struct resource *res;
void __iomem *addr[3];
struct tango_nfc *nfc;
nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
if (!nfc)
return -ENOMEM;
for (i = 0; i < 3; ++i) {
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
addr[i] = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr[i]))
return PTR_ERR(addr[i]);
}
platform_set_drvdata(pdev, nfc);
nand_hw_control_init(&nfc->hw);
nfc->reg_base = addr[0];
nfc->mem_base = addr[1];
nfc->pbus_base = addr[2];
for (i = 0; i < 2; ++i) {
int err = chip_init(&pdev->dev, nfc, i);
if (err)
return err;
}
nfc->chan = dma_request_chan(&pdev->dev, "mlc_flash_0");
if (IS_ERR(nfc->chan))
return PTR_ERR(nfc->chan);
// TODO: tweak (?) Peripheral Bus setup (timings and CS config)
writel(0x052d0905, nfc->reg_base + NFC_TIMING1);
writel(0x0804062d, nfc->reg_base + NFC_TIMING2);
writel(0x00010204, nfc->reg_base + NFC_XFER_CFG);
writel(0x0404000e, nfc->reg_base + NFC_PKT_0_CFG);
writel(0x0400000e, nfc->reg_base + NFC_PKT_N_CFG);
writel(0x08000006, nfc->reg_base + NFC_BB_CFG);
return 0;
}
static int tango_nand_remove(struct platform_device *pdev)
{
int cs;
struct tango_nfc *nfc = platform_get_drvdata(pdev);
dma_release_channel(nfc->chan);
for (cs = 0; cs < 4; ++cs)
if (nfc->chips[cs] != NULL)
nand_release(&nfc->chips[cs]->chip.mtd);
return 0;
}
static const struct of_device_id tango_nand_ids[] = {
{ .compatible = "sigma,smp8758-nand" },
{ /* sentinel */ }
};
static struct platform_driver tango_nand_driver = {
.probe = tango_nand_probe,
.remove = tango_nand_remove,
.driver = {
.name = "tango-nand",
.of_match_table = tango_nand_ids,
},
};
module_platform_driver(tango_nand_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sigma Designs");
MODULE_DESCRIPTION("Tango NAND Flash controller driver");
More information about the linux-mtd
mailing list