[RFC preliminary v0.1] nfc driver for tango platform

Mason slash.tmp at free.fr
Wed Aug 31 09:38:11 PDT 2016


On 30/08/2016 14:55, Mason wrote:
> 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.

I didn't have time to finish, and make a proper patch, as Boris requested.
But I did fix all the hard-coded values, and I wanted to post the result,
as a reference.

I will try to fix the two hard-coded timings. (Since these are chip-dependent
parameters, I thought they should be passed via the device tree, but Boris
thinks it might be possible to deduce at run-time?)

I'm hoping to post a proper patch (with Kconfig and Makefile updated) some
time tomorrow.

Regards.


#include <linux/io.h>
#include <linux/of.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;
	u32 timing1, timing2, xfer_cfg, pkt_0_cfg, pkt_n_cfg, bb_cfg;
};

#define to_tango_chip(ptr) container_of(ptr, struct tango_chip, chip)

/* Offsets relative to chip->base */
#define PBUS_CMD	0
#define PBUS_ADDR	4
#define PBUS_DATA	8

/* Offsets relative to reg_base */
#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

/* NFC_FLASH_CMD values */
#define NFC_READ	1
#define NFC_WRITE	2

/* NFC_XFER_STATUS values */
#define PAGE_IS_EMPTY	BIT(16)

/* Offsets relative to mem_base */
#define ERROR_REPORT	0x1c0

/* ERROR_REPORT values */
#define DECODE_ERR_ON_PKT_0(v)	(~v & BIT(7))
#define DECODE_ERR_ON_PKT_N(v)	(~v & BIT(15))
#define ERR_COUNT_PKT_0(v)	((v >> 0) & 0x3f)
#define ERR_COUNT_PKT_N(v)	((v >> 8) & 0x3f)

/* Offsets relative to pbus_base */
#define PBUS_CS_CTRL	0x83c
#define PBUS_PAD_MODE	0x8f0

/* PBUS_CS_CTRL values */
#define PBUS_IORDY	BIT(31)

/* PBUS_PAD_MODE values */
#define MODE_RAW	0
#define MODE_MLC	BIT(31)

#define METADATA_SIZE	4
#define BBM_SIZE	6
#define FIELD_ORDER	15

#define XFER_CFG(cs, page_count, steps, metadata_size) \
	((cs) << 24 | (page_count) << 16 | (steps) << 8 | (metadata_size) << 0)

#define PKT_CFG(size, strength) ((size) << 16 | (strength) << 0)

#define BB_CFG(bb_offset, bb_size) ((bb_offset) << 16 | (bb_size) << 0)

#define NFC_BUSY(base) (readl_relaxed(base + NFC_STATUS_REG) & 0xf)

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_relaxed(dat, chip->base + PBUS_CMD);

	if (ctrl & NAND_ALE)
		writeb_relaxed(dat, chip->base + PBUS_ADDR);
}

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 + PBUS_CS_CTRL) & PBUS_IORDY;
}

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 + PBUS_DATA);
}

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 + PBUS_DATA, buf, len);
}

static int decode_error_report(struct tango_nfc *nfc)
{
	u32 status, res;

	status = readl_relaxed(nfc->reg_base + NFC_XFER_STATUS);
	if (status & PAGE_IS_EMPTY)
		return 0;

	res = readl_relaxed(nfc->mem_base + ERROR_REPORT);

	if (DECODE_ERR_ON_PKT_0(res) || DECODE_ERR_ON_PKT_N(res))
		return -EBADMSG;

	return max(ERR_COUNT_PKT_0(res), ERR_COUNT_PKT_N(res));
}

static void tango_dma_callback(void *arg)
{
	complete(arg);
}

static int do_dma(struct nand_chip *nand, int dir, int cmd, void *buf, int len, int page)
{
	struct tango_nfc *nfc = to_tango_nfc(nand->controller);
	struct tango_chip *chip = to_tango_chip(nand);
	struct dma_async_tx_descriptor *desc;
	struct scatterlist sg;
	struct completion tx_done;
	int err = -EIO;

	sg_init_one(&sg, buf, len);
	if (dma_map_sg(nfc->chan->device->dev, &sg, 1, dir) != 1)
		goto leave;

	desc = dmaengine_prep_slave_sg(nfc->chan, &sg, 1, dir, DMA_PREP_INTERRUPT);
	if (!desc)
		goto dma_unmap;

	desc->callback = tango_dma_callback;
	desc->callback_param = &tx_done;
	init_completion(&tx_done);

	writel_relaxed(MODE_MLC, nfc->pbus_base + PBUS_PAD_MODE);

	err = 0;
	dmaengine_submit(desc);
	dma_async_issue_pending(nfc->chan);

	writel_relaxed(chip->timing1,	nfc->reg_base + NFC_TIMING1);
	writel_relaxed(chip->timing2,	nfc->reg_base + NFC_TIMING2);
	writel_relaxed(chip->xfer_cfg,	nfc->reg_base + NFC_XFER_CFG);
	writel_relaxed(chip->pkt_0_cfg,	nfc->reg_base + NFC_PKT_0_CFG);
	writel_relaxed(chip->pkt_n_cfg,	nfc->reg_base + NFC_PKT_N_CFG);
	writel_relaxed(chip->bb_cfg,	nfc->reg_base + NFC_BB_CFG);
	writel_relaxed(page, nfc->reg_base + NFC_ADDR_PAGE);
	writel_relaxed(0, nfc->reg_base + NFC_ADDR_OFFSET);
	writel(cmd, nfc->reg_base + NFC_FLASH_CMD);

	wait_for_completion(&tx_done);

	while (NFC_BUSY(nfc->reg_base))
		cpu_relax();

	writel_relaxed(MODE_RAW, nfc->pbus_base + PBUS_PAD_MODE);
dma_unmap:
	dma_unmap_sg(nfc->chan->device->dev, &sg, 1, dir);
leave:
	return err;
}

static int tango_read_page(struct mtd_info *mtd, struct nand_chip *chip,
			uint8_t *buf, int oob_required, int page)
{
	int err;

	err = do_dma(chip, DMA_FROM_DEVICE, NFC_READ, buf, mtd->writesize, page);
	if (err)
		return err;

	return decode_error_report(to_tango_nfc(chip->controller));
}

static int tango_write_page(struct mtd_info *mtd, struct nand_chip *chip,
			const uint8_t *buf, int oob_required, int page)
{
	return do_dma(chip, DMA_TO_DEVICE, NFC_WRITE, (void *)buf, mtd->writesize, page);
}

static int chip_init(struct device *dev, struct tango_nfc *nfc, struct device_node *np)
{
	int err;
	u32 cs, ecc_bits;
	struct nand_ecc_ctrl *ecc;
	struct tango_chip *p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
	if (!p)
		return -ENOMEM;

	nand_set_flash_node(&p->chip, np);
	err = of_property_read_u32_index(np, "reg", 0, &cs);
	if (err)
		return err;

	if (cs >= 4)
		return -ERANGE;

	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->base			= nfc->pbus_base + (cs * 256);

	ecc		= &p->chip.ecc;
	ecc->mode	= NAND_ECC_HW;
	ecc->algo	= NAND_ECC_BCH;
	ecc->read_page	= tango_read_page;
	ecc->write_page	= tango_write_page;


	err = nand_scan_ident(&p->chip.mtd, 1, NULL);
	if (err)
		return err;

	err = nand_scan_tail(&p->chip.mtd);
	if (err)
		return err;

	ecc_bits = p->chip.ecc.strength * FIELD_ORDER;
	p->chip.ecc.bytes = DIV_ROUND_UP(ecc_bits, 8);

	err = mtd_device_register(&p->chip.mtd, NULL, 0);
	if (err)
		return err;

	nfc->chips[cs] = p;

	p->timing1	= 0x052d0905;
	p->timing2	= 0x0804062d;
	p->xfer_cfg	= XFER_CFG(cs, 1, ecc->steps, METADATA_SIZE);
	p->pkt_0_cfg	= PKT_CFG(ecc->size + METADATA_SIZE, ecc->strength);
	p->pkt_n_cfg	= PKT_CFG(ecc->size, ecc->strength);
	p->bb_cfg	= BB_CFG(p->chip.mtd.writesize, BBM_SIZE);

	return 0;
}

static int tango_nand_probe(struct platform_device *pdev)
{
	int i;
	struct resource *res;
	void __iomem *addr[3];
	struct tango_nfc *nfc;
	struct device_node *np;


	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_each_child_of_node(pdev->dev.of_node, np) {
		int err = chip_init(&pdev->dev, nfc, np);
		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)

	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("Tango4 NAND Flash controller driver");




More information about the linux-mtd mailing list