/** * struct spear_smi - Structure for SMI Device * * @clk: functional clock * @status: current status register of SMI. * @clk_rate: functional clock rate of SMI (default: SMI_MAX_CLOCK_FREQ) * @lock: lock to prevent parallel access of SMI. * @io_base: base address for registers of SMI. * @pdev: platform device * @cmd_complete: queue to wait for command completion of NOR-flash. * @num_flashes: number of flashes actually present on board. * @flash: separate structure for each Serial NOR-flash attached to SMI.
*/ struct spear_smi { struct clk *clk;
u32 status; unsignedlong clk_rate; struct mutex lock; void __iomem *io_base; struct platform_device *pdev;
wait_queue_head_t cmd_complete;
u32 num_flashes; struct spear_snor_flash *flash[MAX_NUM_FLASH_CHIP];
};
/** * struct spear_snor_flash - Structure for Serial NOR Flash * * @bank: Bank number(0, 1, 2, 3) for each NOR-flash. * @dev_id: Device ID of NOR-flash. * @lock: lock to manage flash read, write and erase operations * @mtd: MTD info for each NOR-flash. * @num_parts: Total number of partition in each bank of NOR-flash. * @parts: Partition info for each bank of NOR-flash. * @page_size: Page size of NOR-flash. * @base_addr: Base address of NOR-flash. * @erase_cmd: erase command may vary on different flash types * @fast_mode: flash supports read in fast mode
*/ struct spear_snor_flash {
u32 bank;
u32 dev_id; struct mutex lock; struct mtd_info mtd;
u32 num_parts; struct mtd_partition *parts;
u32 page_size; void __iomem *base_addr;
u8 erase_cmd;
u8 fast_mode;
};
/** * spear_smi_read_sr - Read status register of flash through SMI * @dev: structure of SMI information. * @bank: bank to which flash is connected * * This routine will return the status register of the flash chip present at the * given bank.
*/ staticint spear_smi_read_sr(struct spear_smi *dev, u32 bank)
{ int ret;
u32 ctrlreg1;
mutex_lock(&dev->lock);
dev->status = 0; /* Will be set in interrupt handler */
ctrlreg1 = readl(dev->io_base + SMI_CR1); /* program smi in hw mode */
writel(ctrlreg1 & ~(SW_MODE | WB_MODE), dev->io_base + SMI_CR1);
/* performing a rsr instruction in hw mode */
writel((bank << BANK_SHIFT) | RD_STATUS_REG | TFIE,
dev->io_base + SMI_CR2);
/* wait for tff */
ret = wait_event_interruptible_timeout(dev->cmd_complete,
dev->status & TFF, SMI_CMD_TIMEOUT);
/* copy dev->status (lower 16 bits) in order to release lock */ if (ret > 0)
ret = dev->status & 0xffff; elseif (ret == 0)
ret = -ETIMEDOUT;
/* restore the ctrl regs state */
writel(ctrlreg1, dev->io_base + SMI_CR1);
writel(0, dev->io_base + SMI_CR2);
mutex_unlock(&dev->lock);
return ret;
}
/** * spear_smi_wait_till_ready - wait till flash is ready * @dev: structure of SMI information. * @bank: flash corresponding to this bank * @timeout: timeout for busy wait condition * * This routine checks for WIP (write in progress) bit in Status register * If successful the routine returns 0 else -EBUSY
*/ staticint spear_smi_wait_till_ready(struct spear_smi *dev, u32 bank, unsignedlong timeout)
{ unsignedlong finish; int status;
finish = jiffies + timeout; do {
status = spear_smi_read_sr(dev, bank); if (status < 0) { if (status == -ETIMEDOUT) continue; /* try till finish */ return status;
} elseif (!(status & SR_WIP)) { return 0;
}
cond_resched();
} while (!time_after_eq(jiffies, finish));
dev_err(&dev->pdev->dev, "smi controller is busy, timeout\n"); return -EBUSY;
}
/** * spear_smi_int_handler - SMI Interrupt Handler. * @irq: irq number * @dev_id: structure of SMI device, embedded in dev_id. * * The handler clears all interrupt conditions and records the status in * dev->status which is used by the driver later.
*/ static irqreturn_t spear_smi_int_handler(int irq, void *dev_id)
{
u32 status = 0; struct spear_smi *dev = dev_id;
status = readl(dev->io_base + SMI_SR);
if (unlikely(!status)) return IRQ_NONE;
/* clear all interrupt conditions */
writel(0, dev->io_base + SMI_SR);
/* copy the status register in dev->status */
dev->status |= status;
/* send the completion */
wake_up_interruptible(&dev->cmd_complete);
return IRQ_HANDLED;
}
/** * spear_smi_hw_init - initializes the smi controller. * @dev: structure of smi device * * this routine initializes the smi controller wit the default values
*/ staticvoid spear_smi_hw_init(struct spear_smi *dev)
{ unsignedlong rate = 0;
u32 prescale = 0;
u32 val;
rate = clk_get_rate(dev->clk);
/* functional clock of smi */
prescale = DIV_ROUND_UP(rate, dev->clk_rate);
/* * setting the standard values, fast mode, prescaler for * SMI_MAX_CLOCK_FREQ (50MHz) operation and bank enable
*/
val = HOLD1 | BANK_EN | DSEL_TIME | (prescale << 8);
/** * get_flash_index - match chip id from a flash list. * @flash_id: a valid nor flash chip id obtained from board. * * try to validate the chip id by matching from a list, if not found then simply * returns negative. In case of success returns index in to the flash devices * array.
*/ staticint get_flash_index(u32 flash_id)
{ int index;
/* Matches chip-id to entire list of 'serial-nor flash' ids */ for (index = 0; index < ARRAY_SIZE(flash_devices); index++) { if (flash_devices[index].device_id == flash_id) return index;
}
/* Memory chip is not listed and not supported */ return -ENODEV;
}
/** * spear_smi_write_enable - Enable the flash to do write operation * @dev: structure of SMI device * @bank: enable write for flash connected to this bank * * Set write enable latch with Write Enable command. * Returns 0 on success.
*/ staticint spear_smi_write_enable(struct spear_smi *dev, u32 bank)
{ int ret;
u32 ctrlreg1;
mutex_lock(&dev->lock);
dev->status = 0; /* Will be set in interrupt handler */
ctrlreg1 = readl(dev->io_base + SMI_CR1); /* program smi in h/w mode */
writel(ctrlreg1 & ~SW_MODE, dev->io_base + SMI_CR1);
/* give the flash, write enable command */
writel((bank << BANK_SHIFT) | WE | TFIE, dev->io_base + SMI_CR2);
ret = wait_event_interruptible_timeout(dev->cmd_complete,
dev->status & TFF, SMI_CMD_TIMEOUT);
/* restore the ctrl regs state */
writel(ctrlreg1, dev->io_base + SMI_CR1);
writel(0, dev->io_base + SMI_CR2);
if (ret == 0) {
ret = -EIO;
dev_err(&dev->pdev->dev, "smi controller failed on write enable\n");
} elseif (ret > 0) { /* check whether write mode status is set for required bank */ if (dev->status & (1 << (bank + WM_SHIFT)))
ret = 0; else {
dev_err(&dev->pdev->dev, "couldn't enable write\n");
ret = -EIO;
}
}
/** * spear_smi_erase_sector - erase one sector of flash * @dev: structure of SMI information * @command: erase command to be send * @bank: bank to which this command needs to be send * @bytes: size of command * * Erase one sector of flash memory at offset ``offset'' which is any * address within the sector which should be erased. * Returns 0 if successful, non-zero otherwise.
*/ staticint spear_smi_erase_sector(struct spear_smi *dev,
u32 bank, u32 command, u32 bytes)
{
u32 ctrlreg1 = 0; int ret;
ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); if (ret) return ret;
ret = spear_smi_write_enable(dev, bank); if (ret) return ret;
/** * spear_mtd_erase - perform flash erase operation as requested by user * @mtd: Provides the memory characteristics * @e_info: Provides the erase information * * Erase an address range on the flash chip. The address range may extend * one or more erase sectors. Return an error is there is a problem erasing.
*/ staticint spear_mtd_erase(struct mtd_info *mtd, struct erase_info *e_info)
{ struct spear_snor_flash *flash = get_flash_data(mtd); struct spear_smi *dev = mtd->priv;
u32 addr, command, bank; int len, ret;
if (!flash || !dev) return -ENODEV;
bank = flash->bank; if (bank > dev->num_flashes - 1) {
dev_err(&dev->pdev->dev, "Invalid Bank Num"); return -EINVAL;
}
addr = e_info->addr;
len = e_info->len;
mutex_lock(&flash->lock);
/* now erase sectors in loop */ while (len) {
command = get_sector_erase_cmd(flash, addr); /* preparing the command for flash */
ret = spear_smi_erase_sector(dev, bank, command, 4); if (ret) {
mutex_unlock(&flash->lock); return ret;
}
addr += mtd->erasesize;
len -= mtd->erasesize;
}
mutex_unlock(&flash->lock);
return 0;
}
/** * spear_mtd_read - performs flash read operation as requested by the user * @mtd: MTD information of the memory bank * @from: Address from which to start read * @len: Number of bytes to be read * @retlen: Fills the Number of bytes actually read * @buf: Fills this after reading * * Read an address range from the flash chip. The address range * may be any size provided it is within the physical boundaries. * Returns 0 on success, non zero otherwise
*/ staticint spear_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u8 *buf)
{ struct spear_snor_flash *flash = get_flash_data(mtd); struct spear_smi *dev = mtd->priv; void __iomem *src;
u32 ctrlreg1, val; int ret;
if (!flash || !dev) return -ENODEV;
if (flash->bank > dev->num_flashes - 1) {
dev_err(&dev->pdev->dev, "Invalid Bank Num"); return -EINVAL;
}
/* select address as per bank number */
src = flash->base_addr + from;
mutex_lock(&flash->lock);
/* wait till previous write/erase is done. */
ret = spear_smi_wait_till_ready(dev, flash->bank, SMI_MAX_TIME_OUT); if (ret) {
mutex_unlock(&flash->lock); return ret;
}
mutex_lock(&dev->lock); /* put smi in hw mode not wbt mode */
ctrlreg1 = val = readl(dev->io_base + SMI_CR1);
val &= ~(SW_MODE | WB_MODE); if (flash->fast_mode)
val |= FAST_MODE;
/* * The purpose of this function is to ensure a memcpy_toio() with byte writes * only. Its structure is inspired from the ARM implementation of _memcpy_toio() * which also does single byte writes but cannot be used here as this is just an * implementation detail and not part of the API. Not mentioning the comment * stating that _memcpy_toio() should be optimized.
*/ staticvoid spear_smi_memcpy_toio_b(volatilevoid __iomem *dest, constvoid *src, size_t len)
{ constunsignedchar *from = src;
while (len) {
len--;
writeb(*from, dest);
from++;
dest++;
}
}
staticinlineint spear_smi_cpy_toio(struct spear_smi *dev, u32 bank, void __iomem *dest, constvoid *src, size_t len)
{ int ret;
u32 ctrlreg1;
/* wait until finished previous write command. */
ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); if (ret) return ret;
/* put smi in write enable */
ret = spear_smi_write_enable(dev, bank); if (ret) return ret;
/* put smi in hw, write burst mode */
mutex_lock(&dev->lock);
/* * In Write Burst mode (WB_MODE), the specs states that writes must be: * - incremental * - of the same size * The ARM implementation of memcpy_toio() will optimize the number of * I/O by using as much 4-byte writes as possible, surrounded by * 2-byte/1-byte access if: * - the destination is not 4-byte aligned * - the length is not a multiple of 4-byte. * Avoid this alternance of write access size by using our own 'byte * access' helper if at least one of the two conditions above is true.
*/ if (IS_ALIGNED(len, sizeof(u32)) &&
IS_ALIGNED((uintptr_t)dest, sizeof(u32)))
memcpy_toio(dest, src, len); else
spear_smi_memcpy_toio_b(dest, src, len);
writel(ctrlreg1, dev->io_base + SMI_CR1);
mutex_unlock(&dev->lock); return 0;
}
/** * spear_mtd_write - performs write operation as requested by the user. * @mtd: MTD information of the memory bank. * @to: Address to write. * @len: Number of bytes to be written. * @retlen: Number of bytes actually wrote. * @buf: Buffer from which the data to be taken. * * Write an address range to the flash chip. Data must be written in * flash_page_size chunks. The address range may be any size provided * it is within the physical boundaries. * Returns 0 on success, non zero otherwise
*/ staticint spear_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u8 *buf)
{ struct spear_snor_flash *flash = get_flash_data(mtd); struct spear_smi *dev = mtd->priv; void __iomem *dest;
u32 page_offset, page_size; int ret;
if (!flash || !dev) return -ENODEV;
if (flash->bank > dev->num_flashes - 1) {
dev_err(&dev->pdev->dev, "Invalid Bank Num"); return -EINVAL;
}
/* select address as per bank number */
dest = flash->base_addr + to;
mutex_lock(&flash->lock);
page_offset = (u32)to % flash->page_size;
/* do if all the bytes fit onto one page */ if (page_offset + len <= flash->page_size) {
ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf, len); if (!ret)
*retlen += len;
} else {
u32 i;
/* the size of data remaining on the first page */
page_size = flash->page_size - page_offset;
ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf,
page_size); if (ret) goto err_write; else
*retlen += page_size;
/* write everything in pagesize chunks */ for (i = page_size; i < len; i += page_size) {
page_size = len - i; if (page_size > flash->page_size)
page_size = flash->page_size;
ret = spear_smi_cpy_toio(dev, flash->bank, dest + i,
buf + i, page_size); if (ret) break; else
*retlen += page_size;
}
}
err_write:
mutex_unlock(&flash->lock);
return ret;
}
/** * spear_smi_probe_flash - Detects the NOR Flash chip. * @dev: structure of SMI information. * @bank: bank on which flash must be probed * * This routine will check whether there exists a flash chip on a given memory * bank ID. * Return index of the probed flash in flash devices structure
*/ staticint spear_smi_probe_flash(struct spear_smi *dev, u32 bank)
{ int ret;
u32 val = 0;
ret = spear_smi_wait_till_ready(dev, bank, SMI_PROBE_TIMEOUT); if (ret) return ret;
mutex_lock(&dev->lock);
dev->status = 0; /* Will be set in interrupt handler */ /* put smi in sw mode */
val = readl(dev->io_base + SMI_CR1);
writel(val | SW_MODE, dev->io_base + SMI_CR1);
#ifndef CONFIG_OF if (flash_info->partitions) {
parts = flash_info->partitions;
count = flash_info->nr_partitions;
} #endif
ret = mtd_device_register(&flash->mtd, parts, count); if (ret) {
dev_err(&dev->pdev->dev, "Err MTD partition=%d\n", ret); return ret;
}
return 0;
}
/** * spear_smi_probe - Entry routine * @pdev: platform device structure * * This is the first routine which gets invoked during booting and does all * initialization/allocation work. The routine looks for available memory banks, * and do proper init for any found one. * Returns 0 on success, non zero otherwise
*/ staticint spear_smi_probe(struct platform_device *pdev)
{ struct device_node *np = pdev->dev.of_node; struct spear_smi_plat_data *pdata = NULL; struct spear_smi *dev; int irq, ret = 0; int i;
if (np) {
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) {
ret = -ENOMEM; goto err;
}
pdev->dev.platform_data = pdata;
ret = spear_smi_probe_config_dt(pdev, np); if (ret) {
ret = -ENODEV;
dev_err(&pdev->dev, "no platform data\n"); goto err;
}
} else {
pdata = dev_get_platdata(&pdev->dev); if (!pdata) {
ret = -ENODEV;
dev_err(&pdev->dev, "no platform data\n"); goto err;
}
}
irq = platform_get_irq(pdev, 0); if (irq < 0) {
ret = -ENODEV; goto err;
}
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) {
ret = -ENOMEM; goto err;
}
dev->io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(dev->io_base)) {
ret = PTR_ERR(dev->io_base); goto err;
}
/* loop for each serial nor-flash which is connected to smi */ for (i = 0; i < dev->num_flashes; i++) {
ret = spear_smi_setup_banks(pdev, i, pdata->np[i]); if (ret) {
dev_err(&dev->pdev->dev, "bank setup failed\n"); goto err;
}
}
return 0;
err: return ret;
}
/** * spear_smi_remove - Exit routine * @pdev: platform device structure * * free all allocations and delete the partitions.
*/ staticvoid spear_smi_remove(struct platform_device *pdev)
{ struct spear_smi *dev; struct spear_snor_flash *flash; int i;
dev = platform_get_drvdata(pdev);
/* clean up for all nor flash */ for (i = 0; i < dev->num_flashes; i++) {
flash = dev->flash[i]; if (!flash) continue;
/* clean up mtd stuff */
WARN_ON(mtd_device_unregister(&flash->mtd));
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.