Skip to content

Commit

Permalink
MTD: NAND: JZ4740: Multi-bank support with autodetection
Browse files Browse the repository at this point in the history
The platform data can now specify which external memory banks to probe
for NAND chips, and in which order. Banks that contain a NAND are used
and the other banks are freed.

Squashed version of development done in jz-2.6.38 branch.
Original patch by Lars-Peter Clausen with some bug fixes from me.
Thanks to Paul Cercueil for the initial autodetection patch.

Signed-off-by: Maarten ter Huurne <maarten@treewalker.org>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/3560/
Acked-By: David Woodhouse <David.Woodhouse@intel.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
  • Loading branch information
Maarten ter Huurne authored and Ralf Baechle committed Jul 23, 2012
1 parent 28a33cb commit 1471d41
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 37 deletions.
4 changes: 4 additions & 0 deletions arch/mips/include/asm/mach-jz4740/jz4740_nand.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>

#define JZ_NAND_NUM_BANKS 4

struct jz_nand_platform_data {
int num_partitions;
struct mtd_partition *partitions;
Expand All @@ -27,6 +29,8 @@ struct jz_nand_platform_data {

unsigned int busy_gpio;

unsigned char banks[JZ_NAND_NUM_BANKS];

void (*ident_callback)(struct platform_device *, struct nand_chip *,
struct mtd_partition **, int *num_partitions);
};
Expand Down
20 changes: 19 additions & 1 deletion arch/mips/jz4740/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,29 @@ static struct resource jz4740_nand_resources[] = {
.flags = IORESOURCE_MEM,
},
{
.name = "bank",
.name = "bank1",
.start = 0x18000000,
.end = 0x180C0000 - 1,
.flags = IORESOURCE_MEM,
},
{
.name = "bank2",
.start = 0x14000000,
.end = 0x140C0000 - 1,
.flags = IORESOURCE_MEM,
},
{
.name = "bank3",
.start = 0x0C000000,
.end = 0x0C0C0000 - 1,
.flags = IORESOURCE_MEM,
},
{
.name = "bank4",
.start = 0x08000000,
.end = 0x080C0000 - 1,
.flags = IORESOURCE_MEM,
},
};

struct platform_device jz4740_nand_device = {
Expand Down
228 changes: 192 additions & 36 deletions drivers/mtd/nand/jz4740_nand.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,22 @@

#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1)
#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1)
#define JZ_NAND_CTRL_ASSERT_CHIP_MASK 0xaa

#define JZ_NAND_MEM_ADDR_OFFSET 0x10000
#define JZ_NAND_MEM_CMD_OFFSET 0x08000
#define JZ_NAND_MEM_ADDR_OFFSET 0x10000

struct jz_nand {
struct mtd_info mtd;
struct nand_chip chip;
void __iomem *base;
struct resource *mem;

void __iomem *bank_base;
struct resource *bank_mem;
unsigned char banks[JZ_NAND_NUM_BANKS];
void __iomem *bank_base[JZ_NAND_NUM_BANKS];
struct resource *bank_mem[JZ_NAND_NUM_BANKS];

int selected_bank;

struct jz_nand_platform_data *pdata;
bool is_reading;
Expand All @@ -74,26 +78,50 @@ static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd)
return container_of(mtd, struct jz_nand, mtd);
}

static void jz_nand_select_chip(struct mtd_info *mtd, int chipnr)
{
struct jz_nand *nand = mtd_to_jz_nand(mtd);
struct nand_chip *chip = mtd->priv;
uint32_t ctrl;
int banknr;

ctrl = readl(nand->base + JZ_REG_NAND_CTRL);
ctrl &= ~JZ_NAND_CTRL_ASSERT_CHIP_MASK;

if (chipnr == -1) {
banknr = -1;
} else {
banknr = nand->banks[chipnr] - 1;
chip->IO_ADDR_R = nand->bank_base[banknr];
chip->IO_ADDR_W = nand->bank_base[banknr];
}
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);

nand->selected_bank = banknr;
}

static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
struct jz_nand *nand = mtd_to_jz_nand(mtd);
struct nand_chip *chip = mtd->priv;
uint32_t reg;
void __iomem *bank_base = nand->bank_base[nand->selected_bank];

BUG_ON(nand->selected_bank < 0);

if (ctrl & NAND_CTRL_CHANGE) {
BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
if (ctrl & NAND_ALE)
chip->IO_ADDR_W = nand->bank_base + JZ_NAND_MEM_ADDR_OFFSET;
bank_base += JZ_NAND_MEM_ADDR_OFFSET;
else if (ctrl & NAND_CLE)
chip->IO_ADDR_W = nand->bank_base + JZ_NAND_MEM_CMD_OFFSET;
else
chip->IO_ADDR_W = nand->bank_base;
bank_base += JZ_NAND_MEM_CMD_OFFSET;
chip->IO_ADDR_W = bank_base;

reg = readl(nand->base + JZ_REG_NAND_CTRL);
if (ctrl & NAND_NCE)
reg |= JZ_NAND_CTRL_ASSERT_CHIP(0);
reg |= JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank);
else
reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(0);
reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank);
writel(reg, nand->base + JZ_REG_NAND_CTRL);
}
if (dat != NAND_CMD_NONE)
Expand Down Expand Up @@ -252,7 +280,7 @@ static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat,
}

static int jz_nand_ioremap_resource(struct platform_device *pdev,
const char *name, struct resource **res, void __iomem **base)
const char *name, struct resource **res, void *__iomem *base)
{
int ret;

Expand Down Expand Up @@ -288,13 +316,99 @@ static int jz_nand_ioremap_resource(struct platform_device *pdev,
return ret;
}

static inline void jz_nand_iounmap_resource(struct resource *res, void __iomem *base)
{
iounmap(base);
release_mem_region(res->start, resource_size(res));
}

static int __devinit jz_nand_detect_bank(struct platform_device *pdev, struct jz_nand *nand, unsigned char bank, size_t chipnr, uint8_t *nand_maf_id, uint8_t *nand_dev_id) {
int ret;
int gpio;
char gpio_name[9];
char res_name[6];
uint32_t ctrl;
struct mtd_info *mtd = &nand->mtd;
struct nand_chip *chip = &nand->chip;

/* Request GPIO port. */
gpio = JZ_GPIO_MEM_CS0 + bank - 1;
sprintf(gpio_name, "NAND CS%d", bank);
ret = gpio_request(gpio, gpio_name);
if (ret) {
dev_warn(&pdev->dev,
"Failed to request %s gpio %d: %d\n",
gpio_name, gpio, ret);
goto notfound_gpio;
}

/* Request I/O resource. */
sprintf(res_name, "bank%d", bank);
ret = jz_nand_ioremap_resource(pdev, res_name,
&nand->bank_mem[bank - 1],
&nand->bank_base[bank - 1]);
if (ret)
goto notfound_resource;

/* Enable chip in bank. */
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_MEM_CS0);
ctrl = readl(nand->base + JZ_REG_NAND_CTRL);
ctrl |= JZ_NAND_CTRL_ENABLE_CHIP(bank - 1);
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);

if (chipnr == 0) {
/* Detect first chip. */
ret = nand_scan_ident(mtd, 1, NULL);
if (ret)
goto notfound_id;

/* Retrieve the IDs from the first chip. */
chip->select_chip(mtd, 0);
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
*nand_maf_id = chip->read_byte(mtd);
*nand_dev_id = chip->read_byte(mtd);
} else {
/* Detect additional chip. */
chip->select_chip(mtd, chipnr);
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
if (*nand_maf_id != chip->read_byte(mtd)
|| *nand_dev_id != chip->read_byte(mtd)) {
ret = -ENODEV;
goto notfound_id;
}

/* Update size of the MTD. */
chip->numchips++;
mtd->size += chip->chipsize;
}

dev_info(&pdev->dev, "Found chip %i on bank %i\n", chipnr, bank);
return 0;

notfound_id:
dev_info(&pdev->dev, "No chip found on bank %i\n", bank);
ctrl &= ~(JZ_NAND_CTRL_ENABLE_CHIP(bank - 1));
writel(ctrl, nand->base + JZ_REG_NAND_CTRL);
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE);
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
nand->bank_base[bank - 1]);
notfound_resource:
gpio_free(gpio);
notfound_gpio:
return ret;
}

static int __devinit jz_nand_probe(struct platform_device *pdev)
{
int ret;
struct jz_nand *nand;
struct nand_chip *chip;
struct mtd_info *mtd;
struct jz_nand_platform_data *pdata = pdev->dev.platform_data;
size_t chipnr, bank_idx;
uint8_t nand_maf_id = 0, nand_dev_id = 0;

nand = kzalloc(sizeof(*nand), GFP_KERNEL);
if (!nand) {
Expand All @@ -305,18 +419,14 @@ static int __devinit jz_nand_probe(struct platform_device *pdev)
ret = jz_nand_ioremap_resource(pdev, "mmio", &nand->mem, &nand->base);
if (ret)
goto err_free;
ret = jz_nand_ioremap_resource(pdev, "bank", &nand->bank_mem,
&nand->bank_base);
if (ret)
goto err_iounmap_mmio;

if (pdata && gpio_is_valid(pdata->busy_gpio)) {
ret = gpio_request(pdata->busy_gpio, "NAND busy pin");
if (ret) {
dev_err(&pdev->dev,
"Failed to request busy gpio %d: %d\n",
pdata->busy_gpio, ret);
goto err_iounmap_mem;
goto err_iounmap_mmio;
}
}

Expand All @@ -339,22 +449,51 @@ static int __devinit jz_nand_probe(struct platform_device *pdev)

chip->chip_delay = 50;
chip->cmd_ctrl = jz_nand_cmd_ctrl;
chip->select_chip = jz_nand_select_chip;

if (pdata && gpio_is_valid(pdata->busy_gpio))
chip->dev_ready = jz_nand_dev_ready;

chip->IO_ADDR_R = nand->bank_base;
chip->IO_ADDR_W = nand->bank_base;

nand->pdata = pdata;
platform_set_drvdata(pdev, nand);

writel(JZ_NAND_CTRL_ENABLE_CHIP(0), nand->base + JZ_REG_NAND_CTRL);

ret = nand_scan_ident(mtd, 1, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to scan nand\n");
goto err_gpio_free;
/* We are going to autodetect NAND chips in the banks specified in the
* platform data. Although nand_scan_ident() can detect multiple chips,
* it requires those chips to be numbered consecuitively, which is not
* always the case for external memory banks. And a fixed chip-to-bank
* mapping is not practical either, since for example Dingoo units
* produced at different times have NAND chips in different banks.
*/
chipnr = 0;
for (bank_idx = 0; bank_idx < JZ_NAND_NUM_BANKS; bank_idx++) {
unsigned char bank;

/* If there is no platform data, look for NAND in bank 1,
* which is the most likely bank since it is the only one
* that can be booted from.
*/
bank = pdata ? pdata->banks[bank_idx] : bank_idx ^ 1;
if (bank == 0)
break;
if (bank > JZ_NAND_NUM_BANKS) {
dev_warn(&pdev->dev,
"Skipping non-existing bank: %d\n", bank);
continue;
}
/* The detection routine will directly or indirectly call
* jz_nand_select_chip(), so nand->banks has to contain the
* bank we're checking.
*/
nand->banks[chipnr] = bank;
if (jz_nand_detect_bank(pdev, nand, bank, chipnr,
&nand_maf_id, &nand_dev_id) == 0)
chipnr++;
else
nand->banks[chipnr] = 0;
}
if (chipnr == 0) {
dev_err(&pdev->dev, "No NAND chips found\n");
goto err_gpio_busy;
}

if (pdata && pdata->ident_callback) {
Expand All @@ -364,8 +503,8 @@ static int __devinit jz_nand_probe(struct platform_device *pdev)

ret = nand_scan_tail(mtd);
if (ret) {
dev_err(&pdev->dev, "Failed to scan nand\n");
goto err_gpio_free;
dev_err(&pdev->dev, "Failed to scan NAND\n");
goto err_unclaim_banks;
}

ret = mtd_device_parse_register(mtd, NULL, NULL,
Expand All @@ -382,14 +521,21 @@ static int __devinit jz_nand_probe(struct platform_device *pdev)
return 0;

err_nand_release:
nand_release(&nand->mtd);
err_gpio_free:
nand_release(mtd);
err_unclaim_banks:
while (chipnr--) {
unsigned char bank = nand->banks[chipnr];
gpio_free(JZ_GPIO_MEM_CS0 + bank - 1);
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
nand->bank_base[bank - 1]);
}
writel(0, nand->base + JZ_REG_NAND_CTRL);
err_gpio_busy:
if (pdata && gpio_is_valid(pdata->busy_gpio))
gpio_free(pdata->busy_gpio);
platform_set_drvdata(pdev, NULL);
gpio_free(pdata->busy_gpio);
err_iounmap_mem:
iounmap(nand->bank_base);
err_iounmap_mmio:
iounmap(nand->base);
jz_nand_iounmap_resource(nand->mem, nand->base);
err_free:
kfree(nand);
return ret;
Expand All @@ -398,16 +544,26 @@ static int __devinit jz_nand_probe(struct platform_device *pdev)
static int __devexit jz_nand_remove(struct platform_device *pdev)
{
struct jz_nand *nand = platform_get_drvdata(pdev);
struct jz_nand_platform_data *pdata = pdev->dev.platform_data;
size_t i;

nand_release(&nand->mtd);

/* Deassert and disable all chips */
writel(0, nand->base + JZ_REG_NAND_CTRL);

iounmap(nand->bank_base);
release_mem_region(nand->bank_mem->start, resource_size(nand->bank_mem));
iounmap(nand->base);
release_mem_region(nand->mem->start, resource_size(nand->mem));
for (i = 0; i < JZ_NAND_NUM_BANKS; ++i) {
unsigned char bank = nand->banks[i];
if (bank != 0) {
jz_nand_iounmap_resource(nand->bank_mem[bank - 1],
nand->bank_base[bank - 1]);
gpio_free(JZ_GPIO_MEM_CS0 + bank - 1);
}
}
if (pdata && gpio_is_valid(pdata->busy_gpio))
gpio_free(pdata->busy_gpio);

jz_nand_iounmap_resource(nand->mem, nand->base);

platform_set_drvdata(pdev, NULL);
kfree(nand);
Expand Down

0 comments on commit 1471d41

Please sign in to comment.