18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * TXx9 NAND flash memory controller driver
48c2ecf20Sopenharmony_ci * Based on RBTX49xx patch from CELF patch archive.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * (C) Copyright TOSHIBA CORPORATION 2004-2007
78c2ecf20Sopenharmony_ci * All Rights Reserved.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/err.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/delay.h>
158c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h>
168c2ecf20Sopenharmony_ci#include <linux/mtd/rawnand.h>
178c2ecf20Sopenharmony_ci#include <linux/mtd/nand_ecc.h>
188c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h>
198c2ecf20Sopenharmony_ci#include <linux/io.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_data/txx9/ndfmc.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/* TXX9 NDFMC Registers */
238c2ecf20Sopenharmony_ci#define TXX9_NDFDTR	0x00
248c2ecf20Sopenharmony_ci#define TXX9_NDFMCR	0x04
258c2ecf20Sopenharmony_ci#define TXX9_NDFSR	0x08
268c2ecf20Sopenharmony_ci#define TXX9_NDFISR	0x0c
278c2ecf20Sopenharmony_ci#define TXX9_NDFIMR	0x10
288c2ecf20Sopenharmony_ci#define TXX9_NDFSPR	0x14
298c2ecf20Sopenharmony_ci#define TXX9_NDFRSTR	0x18	/* not TX4939 */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* NDFMCR : NDFMC Mode Control */
328c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_WE	0x80
338c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ECC_ALL	0x60
348c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ECC_RESET	0x60
358c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ECC_READ	0x40
368c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ECC_ON	0x20
378c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ECC_OFF	0x00
388c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_CE	0x10
398c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_BSPRT	0x04	/* TX4925/TX4926 only */
408c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_ALE	0x02
418c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_CLE	0x01
428c2ecf20Sopenharmony_ci/* TX4939 only */
438c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_X16	0x0400
448c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_DMAREQ_MASK	0x0300
458c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_DMAREQ_NODMA	0x0000
468c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_DMAREQ_128	0x0100
478c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_DMAREQ_256	0x0200
488c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_DMAREQ_512	0x0300
498c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_CS_MASK	0x0c
508c2ecf20Sopenharmony_ci#define TXX9_NDFMCR_CS(ch)	((ch) << 2)
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/* NDFMCR : NDFMC Status */
538c2ecf20Sopenharmony_ci#define TXX9_NDFSR_BUSY	0x80
548c2ecf20Sopenharmony_ci/* TX4939 only */
558c2ecf20Sopenharmony_ci#define TXX9_NDFSR_DMARUN	0x40
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* NDFMCR : NDFMC Reset */
588c2ecf20Sopenharmony_ci#define TXX9_NDFRSTR_RST	0x01
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistruct txx9ndfmc_priv {
618c2ecf20Sopenharmony_ci	struct platform_device *dev;
628c2ecf20Sopenharmony_ci	struct nand_chip chip;
638c2ecf20Sopenharmony_ci	int cs;
648c2ecf20Sopenharmony_ci	const char *mtdname;
658c2ecf20Sopenharmony_ci};
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci#define MAX_TXX9NDFMC_DEV	4
688c2ecf20Sopenharmony_cistruct txx9ndfmc_drvdata {
698c2ecf20Sopenharmony_ci	struct mtd_info *mtds[MAX_TXX9NDFMC_DEV];
708c2ecf20Sopenharmony_ci	void __iomem *base;
718c2ecf20Sopenharmony_ci	unsigned char hold;	/* in gbusclock */
728c2ecf20Sopenharmony_ci	unsigned char spw;	/* in gbusclock */
738c2ecf20Sopenharmony_ci	struct nand_controller controller;
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic struct platform_device *mtd_to_platdev(struct mtd_info *mtd)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct nand_chip *chip = mtd_to_nand(mtd);
798c2ecf20Sopenharmony_ci	struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip);
808c2ecf20Sopenharmony_ci	return txx9_priv->dev;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic void __iomem *ndregaddr(struct platform_device *dev, unsigned int reg)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
868c2ecf20Sopenharmony_ci	struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return drvdata->base + (reg << plat->shift);
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic u32 txx9ndfmc_read(struct platform_device *dev, unsigned int reg)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	return __raw_readl(ndregaddr(dev, reg));
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic void txx9ndfmc_write(struct platform_device *dev,
978c2ecf20Sopenharmony_ci			    u32 val, unsigned int reg)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	__raw_writel(val, ndregaddr(dev, reg));
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic uint8_t txx9ndfmc_read_byte(struct nand_chip *chip)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return txx9ndfmc_read(dev, TXX9_NDFDTR);
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void txx9ndfmc_write_buf(struct nand_chip *chip, const uint8_t *buf,
1108c2ecf20Sopenharmony_ci				int len)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
1138c2ecf20Sopenharmony_ci	void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
1148c2ecf20Sopenharmony_ci	u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_WE, TXX9_NDFMCR);
1178c2ecf20Sopenharmony_ci	while (len--)
1188c2ecf20Sopenharmony_ci		__raw_writel(*buf++, ndfdtr);
1198c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic void txx9ndfmc_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
1258c2ecf20Sopenharmony_ci	void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	while (len--)
1288c2ecf20Sopenharmony_ci		*buf++ = __raw_readl(ndfdtr);
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic void txx9ndfmc_cmd_ctrl(struct nand_chip *chip, int cmd,
1328c2ecf20Sopenharmony_ci			       unsigned int ctrl)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip);
1358c2ecf20Sopenharmony_ci	struct platform_device *dev = txx9_priv->dev;
1368c2ecf20Sopenharmony_ci	struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	if (ctrl & NAND_CTRL_CHANGE) {
1398c2ecf20Sopenharmony_ci		u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		mcr &= ~(TXX9_NDFMCR_CLE | TXX9_NDFMCR_ALE | TXX9_NDFMCR_CE);
1428c2ecf20Sopenharmony_ci		mcr |= ctrl & NAND_CLE ? TXX9_NDFMCR_CLE : 0;
1438c2ecf20Sopenharmony_ci		mcr |= ctrl & NAND_ALE ? TXX9_NDFMCR_ALE : 0;
1448c2ecf20Sopenharmony_ci		/* TXX9_NDFMCR_CE bit is 0:high 1:low */
1458c2ecf20Sopenharmony_ci		mcr |= ctrl & NAND_NCE ? TXX9_NDFMCR_CE : 0;
1468c2ecf20Sopenharmony_ci		if (txx9_priv->cs >= 0 && (ctrl & NAND_NCE)) {
1478c2ecf20Sopenharmony_ci			mcr &= ~TXX9_NDFMCR_CS_MASK;
1488c2ecf20Sopenharmony_ci			mcr |= TXX9_NDFMCR_CS(txx9_priv->cs);
1498c2ecf20Sopenharmony_ci		}
1508c2ecf20Sopenharmony_ci		txx9ndfmc_write(dev, mcr, TXX9_NDFMCR);
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci	if (cmd != NAND_CMD_NONE)
1538c2ecf20Sopenharmony_ci		txx9ndfmc_write(dev, cmd & 0xff, TXX9_NDFDTR);
1548c2ecf20Sopenharmony_ci	if (plat->flags & NDFMC_PLAT_FLAG_DUMMYWRITE) {
1558c2ecf20Sopenharmony_ci		/* dummy write to update external latch */
1568c2ecf20Sopenharmony_ci		if ((ctrl & NAND_CTRL_CHANGE) && cmd == NAND_CMD_NONE)
1578c2ecf20Sopenharmony_ci			txx9ndfmc_write(dev, 0, TXX9_NDFDTR);
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic int txx9ndfmc_dev_ready(struct nand_chip *chip)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic int txx9ndfmc_calculate_ecc(struct nand_chip *chip, const uint8_t *dat,
1698c2ecf20Sopenharmony_ci				   uint8_t *ecc_code)
1708c2ecf20Sopenharmony_ci{
1718c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
1728c2ecf20Sopenharmony_ci	int eccbytes;
1738c2ecf20Sopenharmony_ci	u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	mcr &= ~TXX9_NDFMCR_ECC_ALL;
1768c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
1778c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR);
1788c2ecf20Sopenharmony_ci	for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) {
1798c2ecf20Sopenharmony_ci		ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR);
1808c2ecf20Sopenharmony_ci		ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR);
1818c2ecf20Sopenharmony_ci		ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR);
1828c2ecf20Sopenharmony_ci		ecc_code += 3;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
1858c2ecf20Sopenharmony_ci	return 0;
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_cistatic int txx9ndfmc_correct_data(struct nand_chip *chip, unsigned char *buf,
1898c2ecf20Sopenharmony_ci				  unsigned char *read_ecc,
1908c2ecf20Sopenharmony_ci				  unsigned char *calc_ecc)
1918c2ecf20Sopenharmony_ci{
1928c2ecf20Sopenharmony_ci	int eccsize;
1938c2ecf20Sopenharmony_ci	int corrected = 0;
1948c2ecf20Sopenharmony_ci	int stat;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) {
1978c2ecf20Sopenharmony_ci		stat = __nand_correct_data(buf, read_ecc, calc_ecc, 256,
1988c2ecf20Sopenharmony_ci					   false);
1998c2ecf20Sopenharmony_ci		if (stat < 0)
2008c2ecf20Sopenharmony_ci			return stat;
2018c2ecf20Sopenharmony_ci		corrected += stat;
2028c2ecf20Sopenharmony_ci		buf += 256;
2038c2ecf20Sopenharmony_ci		read_ecc += 3;
2048c2ecf20Sopenharmony_ci		calc_ecc += 3;
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci	return corrected;
2078c2ecf20Sopenharmony_ci}
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cistatic void txx9ndfmc_enable_hwecc(struct nand_chip *chip, int mode)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip));
2128c2ecf20Sopenharmony_ci	u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	mcr &= ~TXX9_NDFMCR_ECC_ALL;
2158c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_RESET, TXX9_NDFMCR);
2168c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
2178c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_ON, TXX9_NDFMCR);
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic void txx9ndfmc_initialize(struct platform_device *dev)
2218c2ecf20Sopenharmony_ci{
2228c2ecf20Sopenharmony_ci	struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
2238c2ecf20Sopenharmony_ci	struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
2248c2ecf20Sopenharmony_ci	int tmout = 100;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	if (plat->flags & NDFMC_PLAT_FLAG_NO_RSTR)
2278c2ecf20Sopenharmony_ci		; /* no NDFRSTR.  Write to NDFSPR resets the NDFMC. */
2288c2ecf20Sopenharmony_ci	else {
2298c2ecf20Sopenharmony_ci		/* reset NDFMC */
2308c2ecf20Sopenharmony_ci		txx9ndfmc_write(dev,
2318c2ecf20Sopenharmony_ci				txx9ndfmc_read(dev, TXX9_NDFRSTR) |
2328c2ecf20Sopenharmony_ci				TXX9_NDFRSTR_RST,
2338c2ecf20Sopenharmony_ci				TXX9_NDFRSTR);
2348c2ecf20Sopenharmony_ci		while (txx9ndfmc_read(dev, TXX9_NDFRSTR) & TXX9_NDFRSTR_RST) {
2358c2ecf20Sopenharmony_ci			if (--tmout == 0) {
2368c2ecf20Sopenharmony_ci				dev_err(&dev->dev, "reset failed.\n");
2378c2ecf20Sopenharmony_ci				break;
2388c2ecf20Sopenharmony_ci			}
2398c2ecf20Sopenharmony_ci			udelay(1);
2408c2ecf20Sopenharmony_ci		}
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci	/* setup Hold Time, Strobe Pulse Width */
2438c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev, (drvdata->hold << 4) | drvdata->spw, TXX9_NDFSPR);
2448c2ecf20Sopenharmony_ci	txx9ndfmc_write(dev,
2458c2ecf20Sopenharmony_ci			(plat->flags & NDFMC_PLAT_FLAG_USE_BSPRT) ?
2468c2ecf20Sopenharmony_ci			TXX9_NDFMCR_BSPRT : 0, TXX9_NDFMCR);
2478c2ecf20Sopenharmony_ci}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \
2508c2ecf20Sopenharmony_ci	DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000)
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic int txx9ndfmc_attach_chip(struct nand_chip *chip)
2538c2ecf20Sopenharmony_ci{
2548c2ecf20Sopenharmony_ci	struct mtd_info *mtd = nand_to_mtd(chip);
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
2578c2ecf20Sopenharmony_ci		return 0;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	chip->ecc.strength = 1;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	if (mtd->writesize >= 512) {
2628c2ecf20Sopenharmony_ci		chip->ecc.size = 512;
2638c2ecf20Sopenharmony_ci		chip->ecc.bytes = 6;
2648c2ecf20Sopenharmony_ci	} else {
2658c2ecf20Sopenharmony_ci		chip->ecc.size = 256;
2668c2ecf20Sopenharmony_ci		chip->ecc.bytes = 3;
2678c2ecf20Sopenharmony_ci	}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	chip->ecc.calculate = txx9ndfmc_calculate_ecc;
2708c2ecf20Sopenharmony_ci	chip->ecc.correct = txx9ndfmc_correct_data;
2718c2ecf20Sopenharmony_ci	chip->ecc.hwctl = txx9ndfmc_enable_hwecc;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return 0;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic const struct nand_controller_ops txx9ndfmc_controller_ops = {
2778c2ecf20Sopenharmony_ci	.attach_chip = txx9ndfmc_attach_chip,
2788c2ecf20Sopenharmony_ci};
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistatic int __init txx9ndfmc_probe(struct platform_device *dev)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
2838c2ecf20Sopenharmony_ci	int hold, spw;
2848c2ecf20Sopenharmony_ci	int i;
2858c2ecf20Sopenharmony_ci	struct txx9ndfmc_drvdata *drvdata;
2868c2ecf20Sopenharmony_ci	unsigned long gbusclk = plat->gbus_clock;
2878c2ecf20Sopenharmony_ci	struct resource *res;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL);
2908c2ecf20Sopenharmony_ci	if (!drvdata)
2918c2ecf20Sopenharmony_ci		return -ENOMEM;
2928c2ecf20Sopenharmony_ci	res = platform_get_resource(dev, IORESOURCE_MEM, 0);
2938c2ecf20Sopenharmony_ci	drvdata->base = devm_ioremap_resource(&dev->dev, res);
2948c2ecf20Sopenharmony_ci	if (IS_ERR(drvdata->base))
2958c2ecf20Sopenharmony_ci		return PTR_ERR(drvdata->base);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	hold = plat->hold ?: 20; /* tDH */
2988c2ecf20Sopenharmony_ci	spw = plat->spw ?: 90; /* max(tREADID, tWP, tRP) */
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	hold = TXX9NDFMC_NS_TO_CYC(gbusclk, hold);
3018c2ecf20Sopenharmony_ci	spw = TXX9NDFMC_NS_TO_CYC(gbusclk, spw);
3028c2ecf20Sopenharmony_ci	if (plat->flags & NDFMC_PLAT_FLAG_HOLDADD)
3038c2ecf20Sopenharmony_ci		hold -= 2;	/* actual hold time : (HOLD + 2) BUSCLK */
3048c2ecf20Sopenharmony_ci	spw -= 1;	/* actual wait time : (SPW + 1) BUSCLK */
3058c2ecf20Sopenharmony_ci	hold = clamp(hold, 1, 15);
3068c2ecf20Sopenharmony_ci	drvdata->hold = hold;
3078c2ecf20Sopenharmony_ci	spw = clamp(spw, 1, 15);
3088c2ecf20Sopenharmony_ci	drvdata->spw = spw;
3098c2ecf20Sopenharmony_ci	dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n",
3108c2ecf20Sopenharmony_ci		 (gbusclk + 500000) / 1000000, hold, spw);
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	nand_controller_init(&drvdata->controller);
3138c2ecf20Sopenharmony_ci	drvdata->controller.ops = &txx9ndfmc_controller_ops;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, drvdata);
3168c2ecf20Sopenharmony_ci	txx9ndfmc_initialize(dev);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
3198c2ecf20Sopenharmony_ci		struct txx9ndfmc_priv *txx9_priv;
3208c2ecf20Sopenharmony_ci		struct nand_chip *chip;
3218c2ecf20Sopenharmony_ci		struct mtd_info *mtd;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci		if (!(plat->ch_mask & (1 << i)))
3248c2ecf20Sopenharmony_ci			continue;
3258c2ecf20Sopenharmony_ci		txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv),
3268c2ecf20Sopenharmony_ci				    GFP_KERNEL);
3278c2ecf20Sopenharmony_ci		if (!txx9_priv)
3288c2ecf20Sopenharmony_ci			continue;
3298c2ecf20Sopenharmony_ci		chip = &txx9_priv->chip;
3308c2ecf20Sopenharmony_ci		mtd = nand_to_mtd(chip);
3318c2ecf20Sopenharmony_ci		mtd->dev.parent = &dev->dev;
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci		chip->legacy.read_byte = txx9ndfmc_read_byte;
3348c2ecf20Sopenharmony_ci		chip->legacy.read_buf = txx9ndfmc_read_buf;
3358c2ecf20Sopenharmony_ci		chip->legacy.write_buf = txx9ndfmc_write_buf;
3368c2ecf20Sopenharmony_ci		chip->legacy.cmd_ctrl = txx9ndfmc_cmd_ctrl;
3378c2ecf20Sopenharmony_ci		chip->legacy.dev_ready = txx9ndfmc_dev_ready;
3388c2ecf20Sopenharmony_ci		chip->legacy.chip_delay = 100;
3398c2ecf20Sopenharmony_ci		chip->controller = &drvdata->controller;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci		nand_set_controller_data(chip, txx9_priv);
3428c2ecf20Sopenharmony_ci		txx9_priv->dev = dev;
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci		if (plat->ch_mask != 1) {
3458c2ecf20Sopenharmony_ci			txx9_priv->cs = i;
3468c2ecf20Sopenharmony_ci			txx9_priv->mtdname = kasprintf(GFP_KERNEL, "%s.%u",
3478c2ecf20Sopenharmony_ci						       dev_name(&dev->dev), i);
3488c2ecf20Sopenharmony_ci		} else {
3498c2ecf20Sopenharmony_ci			txx9_priv->cs = -1;
3508c2ecf20Sopenharmony_ci			txx9_priv->mtdname = kstrdup(dev_name(&dev->dev),
3518c2ecf20Sopenharmony_ci						     GFP_KERNEL);
3528c2ecf20Sopenharmony_ci		}
3538c2ecf20Sopenharmony_ci		if (!txx9_priv->mtdname) {
3548c2ecf20Sopenharmony_ci			kfree(txx9_priv);
3558c2ecf20Sopenharmony_ci			dev_err(&dev->dev, "Unable to allocate MTD name.\n");
3568c2ecf20Sopenharmony_ci			continue;
3578c2ecf20Sopenharmony_ci		}
3588c2ecf20Sopenharmony_ci		if (plat->wide_mask & (1 << i))
3598c2ecf20Sopenharmony_ci			chip->options |= NAND_BUSWIDTH_16;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci		if (nand_scan(chip, 1)) {
3628c2ecf20Sopenharmony_ci			kfree(txx9_priv->mtdname);
3638c2ecf20Sopenharmony_ci			kfree(txx9_priv);
3648c2ecf20Sopenharmony_ci			continue;
3658c2ecf20Sopenharmony_ci		}
3668c2ecf20Sopenharmony_ci		mtd->name = txx9_priv->mtdname;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci		mtd_device_register(mtd, NULL, 0);
3698c2ecf20Sopenharmony_ci		drvdata->mtds[i] = mtd;
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	return 0;
3738c2ecf20Sopenharmony_ci}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_cistatic int __exit txx9ndfmc_remove(struct platform_device *dev)
3768c2ecf20Sopenharmony_ci{
3778c2ecf20Sopenharmony_ci	struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
3788c2ecf20Sopenharmony_ci	int ret, i;
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	if (!drvdata)
3818c2ecf20Sopenharmony_ci		return 0;
3828c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
3838c2ecf20Sopenharmony_ci		struct mtd_info *mtd = drvdata->mtds[i];
3848c2ecf20Sopenharmony_ci		struct nand_chip *chip;
3858c2ecf20Sopenharmony_ci		struct txx9ndfmc_priv *txx9_priv;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci		if (!mtd)
3888c2ecf20Sopenharmony_ci			continue;
3898c2ecf20Sopenharmony_ci		chip = mtd_to_nand(mtd);
3908c2ecf20Sopenharmony_ci		txx9_priv = nand_get_controller_data(chip);
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci		ret = mtd_device_unregister(nand_to_mtd(chip));
3938c2ecf20Sopenharmony_ci		WARN_ON(ret);
3948c2ecf20Sopenharmony_ci		nand_cleanup(chip);
3958c2ecf20Sopenharmony_ci		kfree(txx9_priv->mtdname);
3968c2ecf20Sopenharmony_ci		kfree(txx9_priv);
3978c2ecf20Sopenharmony_ci	}
3988c2ecf20Sopenharmony_ci	return 0;
3998c2ecf20Sopenharmony_ci}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
4028c2ecf20Sopenharmony_cistatic int txx9ndfmc_resume(struct platform_device *dev)
4038c2ecf20Sopenharmony_ci{
4048c2ecf20Sopenharmony_ci	if (platform_get_drvdata(dev))
4058c2ecf20Sopenharmony_ci		txx9ndfmc_initialize(dev);
4068c2ecf20Sopenharmony_ci	return 0;
4078c2ecf20Sopenharmony_ci}
4088c2ecf20Sopenharmony_ci#else
4098c2ecf20Sopenharmony_ci#define txx9ndfmc_resume NULL
4108c2ecf20Sopenharmony_ci#endif
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic struct platform_driver txx9ndfmc_driver = {
4138c2ecf20Sopenharmony_ci	.remove		= __exit_p(txx9ndfmc_remove),
4148c2ecf20Sopenharmony_ci	.resume		= txx9ndfmc_resume,
4158c2ecf20Sopenharmony_ci	.driver		= {
4168c2ecf20Sopenharmony_ci		.name	= "txx9ndfmc",
4178c2ecf20Sopenharmony_ci	},
4188c2ecf20Sopenharmony_ci};
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_cimodule_platform_driver_probe(txx9ndfmc_driver, txx9ndfmc_probe);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver");
4248c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:txx9ndfmc");
425