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,73 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig MTD_ONENAND
tristate "OneNAND Device Support"
depends on HAS_IOMEM
help
This enables support for accessing all type of OneNAND flash
devices.
if MTD_ONENAND
config MTD_ONENAND_VERIFY_WRITE
bool "Verify OneNAND page writes"
help
This adds an extra check when data is written to the flash. The
OneNAND flash device internally checks only bits transitioning
from 1 to 0. There is a rare possibility that even though the
device thinks the write was successful, a bit could have been
flipped accidentally due to device wear or something else.
config MTD_ONENAND_GENERIC
tristate "OneNAND Flash device via platform device driver"
help
Support for OneNAND flash via platform device driver.
config MTD_ONENAND_OMAP2
tristate "OneNAND on OMAP2/OMAP3 support"
depends on ARCH_OMAP2 || ARCH_OMAP3 || (COMPILE_TEST && ARM)
depends on OF
depends on OMAP_GPMC
help
Support for a OneNAND flash device connected to an OMAP2/OMAP3 SoC
via the GPMC memory controller.
Enable dmaengine and gpiolib for better performance.
config MTD_ONENAND_SAMSUNG
tristate "OneNAND on Samsung SOC controller support"
depends on ARCH_S3C64XX || ARCH_S5PV210 || COMPILE_TEST
help
Support for a OneNAND flash device connected to Samsung S3C64XX
(using command mapping method) and S5PC110/S5PC210 (using generic
OneNAND method) SoCs.
Choose Y here only if you build for such Samsung SoC.
config MTD_ONENAND_OTP
bool "OneNAND OTP Support"
help
One Block of the NAND Flash Array memory is reserved as
a One-Time Programmable Block memory area.
Also, 1st Block of NAND Flash Array can be used as OTP.
The OTP block can be read, programmed and locked using the same
operations as any other NAND Flash Array memory block.
OTP block cannot be erased.
OTP block is fully-guaranteed to be a valid block.
config MTD_ONENAND_2X_PROGRAM
bool "OneNAND 2X program support"
help
The 2X Program is an extension of Program Operation.
Since the device is equipped with two DataRAMs, and two-plane NAND
Flash memory array, these two component enables simultaneous program
of 4KiB. Plane1 has only even blocks such as block0, block2, block4
while Plane2 has only odd blocks such as block1, block3, block5.
So MTD regards it as 4KiB page size and 256KiB block size
Now the following chips support it. (KFXXX16Q2M)
Demux: KFG2G16Q2M, KFH4G16Q2M, KFW8G16Q2M,
Mux: KFM2G16Q2M, KFN4G16Q2M,
And more recent chips
endif # MTD_ONENAND
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the OneNAND MTD
#
# Core functionality.
obj-$(CONFIG_MTD_ONENAND) += onenand.o
# Board specific.
obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o
obj-$(CONFIG_MTD_ONENAND_OMAP2) += onenand_omap2.o
obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += onenand_samsung.o
onenand-objs = onenand_base.o onenand_bbt.o
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2005 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
* Overview:
* This is a device driver for the OneNAND flash for generic boards.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/onenand.h>
#include <linux/mtd/partitions.h>
#include <linux/io.h>
/*
* Note: Driver name and platform data format have been updated!
*
* This version of the driver is named "onenand-flash" and takes struct
* onenand_platform_data as platform data. The old ARM-specific version
* with the name "onenand" used to take struct flash_platform_data.
*/
#define DRIVER_NAME "onenand-flash"
struct onenand_info {
struct mtd_info mtd;
struct onenand_chip onenand;
};
static int generic_onenand_probe(struct platform_device *pdev)
{
struct onenand_info *info;
struct onenand_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct resource *res = pdev->resource;
unsigned long size = resource_size(res);
int err;
info = kzalloc_obj(struct onenand_info);
if (!info)
return -ENOMEM;
if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) {
err = -EBUSY;
goto out_free_info;
}
info->onenand.base = ioremap(res->start, size);
if (!info->onenand.base) {
err = -ENOMEM;
goto out_release_mem_region;
}
info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL;
err = platform_get_irq(pdev, 0);
if (err < 0)
goto out_iounmap;
info->onenand.irq = err;
info->mtd.dev.parent = &pdev->dev;
info->mtd.priv = &info->onenand;
if (onenand_scan(&info->mtd, 1)) {
err = -ENXIO;
goto out_iounmap;
}
err = mtd_device_register(&info->mtd, pdata ? pdata->parts : NULL,
pdata ? pdata->nr_parts : 0);
platform_set_drvdata(pdev, info);
return 0;
out_iounmap:
iounmap(info->onenand.base);
out_release_mem_region:
release_mem_region(res->start, size);
out_free_info:
kfree(info);
return err;
}
static void generic_onenand_remove(struct platform_device *pdev)
{
struct onenand_info *info = platform_get_drvdata(pdev);
struct resource *res = pdev->resource;
unsigned long size = resource_size(res);
if (info) {
onenand_release(&info->mtd);
release_mem_region(res->start, size);
iounmap(info->onenand.base);
kfree(info);
}
}
static struct platform_driver generic_onenand_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = generic_onenand_probe,
.remove = generic_onenand_remove,
};
module_platform_driver(generic_onenand_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
MODULE_DESCRIPTION("Glue layer for OneNAND flash on generic boards");
MODULE_ALIAS("platform:" DRIVER_NAME);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Bad Block Table support for the OneNAND driver
*
* Copyright(c) 2005 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
* Derived from nand_bbt.c
*
* TODO:
* Split BBT core and chip specific BBT.
*/
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/onenand.h>
#include <linux/export.h>
/**
* check_short_pattern - [GENERIC] check if a pattern is in the buffer
* @buf: the buffer to search
* @len: the length of buffer to search
* @paglen: the pagelength
* @td: search pattern descriptor
*
* Check for a pattern at the given place. Used to search bad block
* tables and good / bad block identifiers. Same as check_pattern, but
* no optional empty check and the pattern is expected to start
* at offset 0.
*
*/
static int check_short_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td)
{
int i;
uint8_t *p = buf;
/* Compare the pattern */
for (i = 0; i < td->len; i++) {
if (p[i] != td->pattern[i])
return -1;
}
return 0;
}
/**
* create_bbt - [GENERIC] Create a bad block table by scanning the device
* @mtd: MTD device structure
* @buf: temporary buffer
* @bd: descriptor for the good/bad block search pattern
* @chip: create the table for a specific chip, -1 read all chips.
* Applies only if NAND_BBT_PERCHIP option is set
*
* Create a bad block table by scanning the device
* for the given good/bad block identify pattern
*/
static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip)
{
struct onenand_chip *this = mtd->priv;
struct bbm_info *bbm = this->bbm;
int i, j, numblocks, len, scanlen;
int startblock;
loff_t from;
size_t readlen;
struct mtd_oob_ops ops = { };
int rgn;
printk(KERN_INFO "Scanning device for bad blocks\n");
len = 2;
/* We need only read few bytes from the OOB area */
scanlen = 0;
readlen = bd->len;
/* chip == -1 case only */
/* Note that numblocks is 2 * (real numblocks) here;
* see i += 2 below as it makses shifting and masking less painful
*/
numblocks = this->chipsize >> (bbm->bbt_erase_shift - 1);
startblock = 0;
from = 0;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooblen = readlen;
ops.oobbuf = buf;
ops.len = ops.ooboffs = ops.retlen = ops.oobretlen = 0;
for (i = startblock; i < numblocks; ) {
int ret;
for (j = 0; j < len; j++) {
/* No need to read pages fully,
* just read required OOB bytes */
ret = onenand_bbt_read_oob(mtd,
from + j * this->writesize + bd->offs, &ops);
/* If it is a initial bad block, just ignore it */
if (ret == ONENAND_BBT_READ_FATAL_ERROR)
return -EIO;
if (ret || check_short_pattern(&buf[j * scanlen],
scanlen, this->writesize, bd)) {
bbm->bbt[i >> 3] |= 0x03 << (i & 0x6);
printk(KERN_INFO "OneNAND eraseblock %d is an "
"initial bad block\n", i >> 1);
mtd->ecc_stats.badblocks++;
break;
}
}
i += 2;
if (FLEXONENAND(this)) {
rgn = flexonenand_region(mtd, from);
from += mtd->eraseregions[rgn].erasesize;
} else
from += (1 << bbm->bbt_erase_shift);
}
return 0;
}
/**
* onenand_memory_bbt - [GENERIC] create a memory based bad block table
* @mtd: MTD device structure
* @bd: descriptor for the good/bad block search pattern
*
* The function creates a memory based bbt by scanning the device
* for manufacturer / software marked good / bad blocks
*/
static inline int onenand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd)
{
struct onenand_chip *this = mtd->priv;
return create_bbt(mtd, this->page_buf, bd, -1);
}
/**
* onenand_isbad_bbt - [OneNAND Interface] Check if a block is bad
* @mtd: MTD device structure
* @offs: offset in the device
* @allowbbt: allow access to bad block table region
*/
static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt)
{
struct onenand_chip *this = mtd->priv;
struct bbm_info *bbm = this->bbm;
int block;
uint8_t res;
/* Get block number * 2 */
block = (int) (onenand_block(this, offs) << 1);
res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03;
pr_debug("onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n",
(unsigned int) offs, block >> 1, res);
switch ((int) res) {
case 0x00: return 0;
case 0x01: return 1;
case 0x02: return allowbbt ? 0 : 1;
}
return 1;
}
/**
* onenand_scan_bbt - [OneNAND Interface] scan, find, read and maybe create bad block table(s)
* @mtd: MTD device structure
* @bd: descriptor for the good/bad block search pattern
*
* The function checks, if a bad block table(s) is/are already
* available. If not it scans the device for manufacturer
* marked good / bad blocks and writes the bad block table(s) to
* the selected place.
*
* The bad block table memory is allocated here. It is freed
* by the onenand_release function.
*
*/
static int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
{
struct onenand_chip *this = mtd->priv;
struct bbm_info *bbm = this->bbm;
int len, ret = 0;
len = this->chipsize >> (this->erase_shift + 2);
/* Allocate memory (2bit per block) and clear the memory bad block table */
bbm->bbt = kzalloc(len, GFP_KERNEL);
if (!bbm->bbt)
return -ENOMEM;
/* Set erase shift */
bbm->bbt_erase_shift = this->erase_shift;
if (!bbm->isbad_bbt)
bbm->isbad_bbt = onenand_isbad_bbt;
/* Scan the device to build a memory based bad block table */
if ((ret = onenand_memory_bbt(mtd, bd))) {
printk(KERN_ERR "onenand_scan_bbt: Can't scan flash and build the RAM-based BBT\n");
kfree(bbm->bbt);
bbm->bbt = NULL;
}
return ret;
}
/*
* Define some generic bad / good block scan pattern which are used
* while scanning a device for factory marked good / bad blocks.
*/
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
static struct nand_bbt_descr largepage_memorybased = {
.options = 0,
.offs = 0,
.len = 2,
.pattern = scan_ff_pattern,
};
/**
* onenand_default_bbt - [OneNAND Interface] Select a default bad block table for the device
* @mtd: MTD device structure
*
* This function selects the default bad block table
* support for the device and calls the onenand_scan_bbt function
*/
int onenand_default_bbt(struct mtd_info *mtd)
{
struct onenand_chip *this = mtd->priv;
struct bbm_info *bbm;
this->bbm = kzalloc_obj(struct bbm_info);
if (!this->bbm)
return -ENOMEM;
bbm = this->bbm;
/* 1KB page has same configuration as 2KB page */
if (!bbm->badblock_pattern)
bbm->badblock_pattern = &largepage_memorybased;
return onenand_scan_bbt(mtd, bbm->badblock_pattern);
}
@@ -0,0 +1,608 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* OneNAND driver for OMAP2 / OMAP3
*
* Copyright © 2005-2006 Nokia Corporation
*
* Author: Jarkko Lavinen <jarkko.lavinen@nokia.com> and Juha Yrjölä
* IRQ and DMA support written by Timo Teras
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/onenand.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/omap-gpmc.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/flash.h>
#define DRIVER_NAME "omap2-onenand"
#define ONENAND_BUFRAM_SIZE (1024 * 5)
struct omap2_onenand {
struct platform_device *pdev;
int gpmc_cs;
unsigned long phys_base;
struct gpio_desc *int_gpiod;
struct mtd_info mtd;
struct onenand_chip onenand;
struct completion irq_done;
struct completion dma_done;
struct dma_chan *dma_chan;
};
static void omap2_onenand_dma_complete_func(void *completion)
{
complete(completion);
}
static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id)
{
struct omap2_onenand *c = dev_id;
complete(&c->irq_done);
return IRQ_HANDLED;
}
static inline unsigned short read_reg(struct omap2_onenand *c, int reg)
{
return readw(c->onenand.base + reg);
}
static inline void write_reg(struct omap2_onenand *c, unsigned short value,
int reg)
{
writew(value, c->onenand.base + reg);
}
static int omap2_onenand_set_cfg(struct omap2_onenand *c,
bool sr, bool sw,
int latency, int burst_len)
{
unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT;
reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT;
switch (burst_len) {
case 0: /* continuous */
break;
case 4:
reg |= ONENAND_SYS_CFG1_BL_4;
break;
case 8:
reg |= ONENAND_SYS_CFG1_BL_8;
break;
case 16:
reg |= ONENAND_SYS_CFG1_BL_16;
break;
case 32:
reg |= ONENAND_SYS_CFG1_BL_32;
break;
default:
return -EINVAL;
}
if (latency > 5)
reg |= ONENAND_SYS_CFG1_HF;
if (latency > 7)
reg |= ONENAND_SYS_CFG1_VHF;
if (sr)
reg |= ONENAND_SYS_CFG1_SYNC_READ;
if (sw)
reg |= ONENAND_SYS_CFG1_SYNC_WRITE;
write_reg(c, reg, ONENAND_REG_SYS_CFG1);
return 0;
}
static int omap2_onenand_get_freq(int ver)
{
switch ((ver >> 4) & 0xf) {
case 0:
return 40;
case 1:
return 54;
case 2:
return 66;
case 3:
return 83;
case 4:
return 104;
}
return -EINVAL;
}
static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr)
{
printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n",
msg, state, ctrl, intr);
}
static void wait_warn(char *msg, int state, unsigned int ctrl,
unsigned int intr)
{
printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x "
"intr 0x%04x\n", msg, state, ctrl, intr);
}
static int omap2_onenand_wait(struct mtd_info *mtd, int state)
{
struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd);
struct onenand_chip *this = mtd->priv;
unsigned int intr = 0;
unsigned int ctrl, ctrl_mask;
unsigned long timeout;
u32 syscfg;
if (state == FL_RESETTING || state == FL_PREPARING_ERASE ||
state == FL_VERIFYING_ERASE) {
int i = 21;
unsigned int intr_flags = ONENAND_INT_MASTER;
switch (state) {
case FL_RESETTING:
intr_flags |= ONENAND_INT_RESET;
break;
case FL_PREPARING_ERASE:
intr_flags |= ONENAND_INT_ERASE;
break;
case FL_VERIFYING_ERASE:
i = 101;
break;
}
while (--i) {
udelay(1);
intr = read_reg(c, ONENAND_REG_INTERRUPT);
if (intr & ONENAND_INT_MASTER)
break;
}
ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS);
if (ctrl & ONENAND_CTRL_ERROR) {
wait_err("controller error", state, ctrl, intr);
return -EIO;
}
if ((intr & intr_flags) == intr_flags)
return 0;
/* Continue in wait for interrupt branch */
}
if (state != FL_READING) {
int result;
/* Turn interrupts on */
syscfg = read_reg(c, ONENAND_REG_SYS_CFG1);
if (!(syscfg & ONENAND_SYS_CFG1_IOBE)) {
syscfg |= ONENAND_SYS_CFG1_IOBE;
write_reg(c, syscfg, ONENAND_REG_SYS_CFG1);
/* Add a delay to let GPIO settle */
syscfg = read_reg(c, ONENAND_REG_SYS_CFG1);
}
reinit_completion(&c->irq_done);
result = gpiod_get_value(c->int_gpiod);
if (result < 0) {
ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS);
intr = read_reg(c, ONENAND_REG_INTERRUPT);
wait_err("gpio error", state, ctrl, intr);
return result;
} else if (result == 0) {
int retry_cnt = 0;
retry:
if (!wait_for_completion_io_timeout(&c->irq_done,
msecs_to_jiffies(20))) {
/* Timeout after 20ms */
ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS);
if (ctrl & ONENAND_CTRL_ONGO &&
!this->ongoing) {
/*
* The operation seems to be still going
* so give it some more time.
*/
retry_cnt += 1;
if (retry_cnt < 3)
goto retry;
intr = read_reg(c,
ONENAND_REG_INTERRUPT);
wait_err("timeout", state, ctrl, intr);
return -EIO;
}
intr = read_reg(c, ONENAND_REG_INTERRUPT);
if ((intr & ONENAND_INT_MASTER) == 0)
wait_warn("timeout", state, ctrl, intr);
}
}
} else {
int retry_cnt = 0;
/* Turn interrupts off */
syscfg = read_reg(c, ONENAND_REG_SYS_CFG1);
syscfg &= ~ONENAND_SYS_CFG1_IOBE;
write_reg(c, syscfg, ONENAND_REG_SYS_CFG1);
timeout = jiffies + msecs_to_jiffies(20);
while (1) {
if (time_before(jiffies, timeout)) {
intr = read_reg(c, ONENAND_REG_INTERRUPT);
if (intr & ONENAND_INT_MASTER)
break;
} else {
/* Timeout after 20ms */
ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS);
if (ctrl & ONENAND_CTRL_ONGO) {
/*
* The operation seems to be still going
* so give it some more time.
*/
retry_cnt += 1;
if (retry_cnt < 3) {
timeout = jiffies +
msecs_to_jiffies(20);
continue;
}
}
break;
}
}
}
intr = read_reg(c, ONENAND_REG_INTERRUPT);
ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS);
if (intr & ONENAND_INT_READ) {
int ecc = read_reg(c, ONENAND_REG_ECC_STATUS);
if (ecc) {
unsigned int addr1, addr8;
addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1);
addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8);
if (ecc & ONENAND_ECC_2BIT_ALL) {
printk(KERN_ERR "onenand_wait: ECC error = "
"0x%04x, addr1 %#x, addr8 %#x\n",
ecc, addr1, addr8);
mtd->ecc_stats.failed++;
return -EBADMSG;
} else if (ecc & ONENAND_ECC_1BIT_ALL) {
printk(KERN_NOTICE "onenand_wait: correctable "
"ECC error = 0x%04x, addr1 %#x, "
"addr8 %#x\n", ecc, addr1, addr8);
mtd->ecc_stats.corrected++;
}
}
} else if (state == FL_READING) {
wait_err("timeout", state, ctrl, intr);
return -EIO;
}
if (ctrl & ONENAND_CTRL_ERROR) {
wait_err("controller error", state, ctrl, intr);
if (ctrl & ONENAND_CTRL_LOCK)
printk(KERN_ERR "onenand_wait: "
"Device is write protected!!!\n");
return -EIO;
}
ctrl_mask = 0xFE9F;
if (this->ongoing)
ctrl_mask &= ~0x8000;
if (ctrl & ctrl_mask)
wait_warn("unexpected controller status", state, ctrl, intr);
return 0;
}
static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area)
{
struct onenand_chip *this = mtd->priv;
if (ONENAND_CURRENT_BUFFERRAM(this)) {
if (area == ONENAND_DATARAM)
return this->writesize;
if (area == ONENAND_SPARERAM)
return mtd->oobsize;
}
return 0;
}
static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c,
dma_addr_t src, dma_addr_t dst,
size_t count)
{
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!tx) {
dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n");
return -EIO;
}
reinit_completion(&c->dma_done);
tx->callback = omap2_onenand_dma_complete_func;
tx->callback_param = &c->dma_done;
cookie = tx->tx_submit(tx);
if (dma_submit_error(cookie)) {
dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n");
return -EIO;
}
dma_async_issue_pending(c->dma_chan);
if (!wait_for_completion_io_timeout(&c->dma_done,
msecs_to_jiffies(20))) {
dmaengine_terminate_sync(c->dma_chan);
return -ETIMEDOUT;
}
return 0;
}
static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area,
unsigned char *buffer, int offset,
size_t count)
{
struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd);
struct onenand_chip *this = mtd->priv;
struct device *dev = &c->pdev->dev;
void *buf = (void *)buffer;
dma_addr_t dma_src, dma_dst;
int bram_offset, err;
size_t xtra;
bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset;
/*
* If the buffer address is not DMA-able, len is not long enough to
* make DMA transfers profitable or if invoked from panic_write()
* fallback to PIO mode.
*/
if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 ||
count < 384 || mtd->oops_panic_write)
goto out_copy;
xtra = count & 3;
if (xtra) {
count -= xtra;
memcpy(buf + count, this->base + bram_offset + count, xtra);
}
dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE);
dma_src = c->phys_base + bram_offset;
if (dma_mapping_error(dev, dma_dst)) {
dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count);
goto out_copy;
}
err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count);
dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE);
if (!err)
return 0;
dev_err(dev, "timeout waiting for DMA\n");
out_copy:
memcpy(buf, this->base + bram_offset, count);
return 0;
}
static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area,
const unsigned char *buffer,
int offset, size_t count)
{
struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd);
struct onenand_chip *this = mtd->priv;
struct device *dev = &c->pdev->dev;
void *buf = (void *)buffer;
dma_addr_t dma_src, dma_dst;
int bram_offset, err;
bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset;
/*
* If the buffer address is not DMA-able, len is not long enough to
* make DMA transfers profitable or if invoked from panic_write()
* fallback to PIO mode.
*/
if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 ||
count < 384 || mtd->oops_panic_write)
goto out_copy;
dma_src = dma_map_single(dev, buf, count, DMA_TO_DEVICE);
dma_dst = c->phys_base + bram_offset;
if (dma_mapping_error(dev, dma_src)) {
dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count);
goto out_copy;
}
err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count);
dma_unmap_page(dev, dma_src, count, DMA_TO_DEVICE);
if (!err)
return 0;
dev_err(dev, "timeout waiting for DMA\n");
out_copy:
memcpy(this->base + bram_offset, buf, count);
return 0;
}
static void omap2_onenand_shutdown(struct platform_device *pdev)
{
struct omap2_onenand *c = dev_get_drvdata(&pdev->dev);
/* With certain content in the buffer RAM, the OMAP boot ROM code
* can recognize the flash chip incorrectly. Zero it out before
* soft reset.
*/
memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE);
}
static int omap2_onenand_probe(struct platform_device *pdev)
{
u32 val;
dma_cap_mask_t mask;
int freq, latency, r;
struct resource *res;
struct omap2_onenand *c;
struct gpmc_onenand_info info;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
r = of_property_read_u32(np, "reg", &val);
if (r) {
dev_err(dev, "reg not found in DT\n");
return r;
}
c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL);
if (!c)
return -ENOMEM;
init_completion(&c->irq_done);
init_completion(&c->dma_done);
c->gpmc_cs = val;
c->onenand.base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(c->onenand.base))
return PTR_ERR(c->onenand.base);
c->phys_base = res->start;
c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN);
if (IS_ERR(c->int_gpiod)) {
/* Just try again if this happens */
return dev_err_probe(dev, PTR_ERR(c->int_gpiod), "error getting gpio\n");
}
if (c->int_gpiod) {
r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod),
omap2_onenand_interrupt,
IRQF_TRIGGER_RISING, "onenand", c);
if (r)
return r;
c->onenand.wait = omap2_onenand_wait;
}
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
c->dma_chan = dma_request_channel(mask, NULL, NULL);
if (c->dma_chan) {
c->onenand.read_bufferram = omap2_onenand_read_bufferram;
c->onenand.write_bufferram = omap2_onenand_write_bufferram;
}
c->pdev = pdev;
c->mtd.priv = &c->onenand;
c->mtd.dev.parent = dev;
mtd_set_of_node(&c->mtd, dev->of_node);
dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n",
c->gpmc_cs, c->phys_base, c->onenand.base,
c->dma_chan ? "DMA" : "PIO");
r = onenand_scan(&c->mtd, 1);
if (r < 0)
goto err_release_dma;
freq = omap2_onenand_get_freq(c->onenand.version_id);
if (freq > 0) {
switch (freq) {
case 104:
latency = 7;
break;
case 83:
latency = 6;
break;
case 66:
latency = 5;
break;
case 56:
latency = 4;
break;
default: /* 40 MHz or lower */
latency = 3;
break;
}
r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs,
freq, latency, &info);
if (r)
goto err_release_onenand;
r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write,
latency, info.burst_len);
if (r)
goto err_release_onenand;
if (info.sync_read || info.sync_write)
dev_info(dev, "optimized timings for %d MHz\n", freq);
}
r = mtd_device_register(&c->mtd, NULL, 0);
if (r)
goto err_release_onenand;
platform_set_drvdata(pdev, c);
return 0;
err_release_onenand:
onenand_release(&c->mtd);
err_release_dma:
if (c->dma_chan)
dma_release_channel(c->dma_chan);
return r;
}
static void omap2_onenand_remove(struct platform_device *pdev)
{
struct omap2_onenand *c = dev_get_drvdata(&pdev->dev);
onenand_release(&c->mtd);
if (c->dma_chan)
dma_release_channel(c->dma_chan);
omap2_onenand_shutdown(pdev);
}
static const struct of_device_id omap2_onenand_id_table[] = {
{ .compatible = "ti,omap2-onenand", },
{},
};
MODULE_DEVICE_TABLE(of, omap2_onenand_id_table);
static struct platform_driver omap2_onenand_driver = {
.probe = omap2_onenand_probe,
.remove = omap2_onenand_remove,
.shutdown = omap2_onenand_shutdown,
.driver = {
.name = DRIVER_NAME,
.of_match_table = omap2_onenand_id_table,
},
};
module_platform_driver(omap2_onenand_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>");
MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2008-2010 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*/
#ifndef __SAMSUNG_ONENAND_H__
#define __SAMSUNG_ONENAND_H__
/*
* OneNAND Controller
*/
#define MEM_CFG_OFFSET 0x0000
#define BURST_LEN_OFFSET 0x0010
#define MEM_RESET_OFFSET 0x0020
#define INT_ERR_STAT_OFFSET 0x0030
#define INT_ERR_MASK_OFFSET 0x0040
#define INT_ERR_ACK_OFFSET 0x0050
#define ECC_ERR_STAT_OFFSET 0x0060
#define MANUFACT_ID_OFFSET 0x0070
#define DEVICE_ID_OFFSET 0x0080
#define DATA_BUF_SIZE_OFFSET 0x0090
#define BOOT_BUF_SIZE_OFFSET 0x00A0
#define BUF_AMOUNT_OFFSET 0x00B0
#define TECH_OFFSET 0x00C0
#define FBA_WIDTH_OFFSET 0x00D0
#define FPA_WIDTH_OFFSET 0x00E0
#define FSA_WIDTH_OFFSET 0x00F0
#define TRANS_SPARE_OFFSET 0x0140
#define DBS_DFS_WIDTH_OFFSET 0x0160
#define INT_PIN_ENABLE_OFFSET 0x01A0
#define ACC_CLOCK_OFFSET 0x01C0
#define FLASH_VER_ID_OFFSET 0x01F0
#define FLASH_AUX_CNTRL_OFFSET 0x0300 /* s3c64xx only */
#define ONENAND_MEM_RESET_HOT 0x3
#define ONENAND_MEM_RESET_COLD 0x2
#define ONENAND_MEM_RESET_WARM 0x1
#define CACHE_OP_ERR (1 << 13)
#define RST_CMP (1 << 12)
#define RDY_ACT (1 << 11)
#define INT_ACT (1 << 10)
#define UNSUP_CMD (1 << 9)
#define LOCKED_BLK (1 << 8)
#define BLK_RW_CMP (1 << 7)
#define ERS_CMP (1 << 6)
#define PGM_CMP (1 << 5)
#define LOAD_CMP (1 << 4)
#define ERS_FAIL (1 << 3)
#define PGM_FAIL (1 << 2)
#define INT_TO (1 << 1)
#define LD_FAIL_ECC_ERR (1 << 0)
#define TSRF (1 << 0)
#endif