blob: 81b056a04e69a725a589ad09f1cd85a44eb2523e [file] [log] [blame]
/*
* drivers/mtd/ambarella_spinor.c
*
* History:
* 2014/03/10 - [cddiao] created file
*
* Copyright (C) 2014-2019, Ambarella, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "spinor.h"
/* Flash opcodes. */
#define OPCODE_WREN 0x06 /* Write enable */
#define OPCODE_RDSR 0x05 /* Read status register */
#define OPCODE_WRSR 0x01 /* Write status register 1 byte */
#define OPCODE_NORM_READ 0x03 /* Read data bytes (low frequency) */
#define OPCODE_FAST_READ 0x0b /* Read data bytes (high frequency) */
#define OPCODE_PP 0x02 /* Page program (up to 256 bytes) */
#define OPCODE_BE_4K 0x20 /* Erase 4KiB block */
#define OPCODE_BE_32K 0x52 /* Erase 32KiB block */
#define OPCODE_CHIP_ERASE 0xc7 /* Erase whole flash chip */
#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */
#define OPCODE_RDID 0x9f /* Read JEDEC ID */
/* Used for SST flashes only. */
#define OPCODE_BP 0x02 /* Byte program */
#define OPCODE_WRDI 0x04 /* Write disable */
#define OPCODE_AAI_WP 0xad /* Auto address increment word program */
/* Used for Macronix flashes only. */
#define OPCODE_EN4B 0xb7 /* Enter 4-byte mode */
#define OPCODE_EX4B 0xe9 /* Exit 4-byte mode */
/* Used for Spansion flashes only. */
#define OPCODE_BRWR 0x17 /* Bank register write */
/* Status Register bits. */
#define SR_WIP 1 /* Write in progress */
#define SR_WEL 2 /* Write enable latch */
/* meaning of other SR_* bits may differ between vendors */
#define SR_BP0 4 /* Block protect 0 */
#define SR_BP1 8 /* Block protect 1 */
#define SR_BP2 0x10 /* Block protect 2 */
#define SR_SRWD 0x80 /* SR write protect */
/* Define max times to check status register before we give up. */
#define MAX_READY_WAIT_JIFFIES (400 * HZ) /* 40s max chip erase */
#define MAX_CMD_SIZE 5
#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
#define PART_DEV_SPINOR (0x08)
static u16 spi_addr_mode = 3;
/****************************************************************************/
static inline struct amb_norflash *mtd_to_amb(struct mtd_info *mtd)
{
return container_of(mtd, struct amb_norflash, mtd);
}
/****************************************************************************/
static int read_sr(struct amb_norflash *flash)
{
ssize_t retval = 0;
u8 code = OPCODE_RDSR;
u8 val;
retval = ambspi_read_reg(flash, 1, code, &val);
if (retval < 0) {
dev_err((const struct device *)&flash->dev, "error %d reading SR\n",
(int) retval);
return retval;
}
return val;
}
/*
* Set write enable latch with Write Enable command.
* Returns negative if error occurred.
*/
int write_enable(struct amb_norflash *flash)
{
u8 code = OPCODE_WREN;
return ambspi_send_cmd(flash, code, 0, 0, 0);
}
/*
* Send write disble instruction to the chip.
*/
static inline int write_disable(struct amb_norflash *flash)
{
u8 code = OPCODE_WRDI;
return ambspi_send_cmd(flash, code, 0, 0, 0);
}
/*
* Enable/disable 4-byte addressing mode.
*/
static inline int set_4byte(struct amb_norflash *flash, u32 jedec_id, int enable)
{
int ret = 0;
switch (JEDEC_MFR(jedec_id)) {
case CFI_MFR_MACRONIX:
case 0xEF: /* winbond */
case 0xC8: /* GD */
flash->command[0] = enable ? OPCODE_EN4B : OPCODE_EX4B;
return ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
case CFI_MFR_ST: /*Micron*/
write_enable(flash);
flash->command[0] = enable ? OPCODE_EN4B : OPCODE_EX4B;
ret = ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
write_disable(flash);
return ret;
default:
/* Spansion style */
flash->command[0] = OPCODE_BRWR;
flash->command[1] = enable << 7;
return ambspi_send_cmd(flash, flash->command[0], 0, flash->command[1], 1);
}
}
/*
mode 1 - enter 4 byte spi addr mode
0 - exit 4 byte spi addr mode
*/
static void check_set_spinor_addr_mode(struct amb_norflash *flash, int mode)
{
if (flash->addr_width == 4) {
if (spi_addr_mode == 3 && mode){
set_4byte(flash, flash->jedec_id, 1);
spi_addr_mode = 4;
} else if (spi_addr_mode == 4 && mode == 0){
set_4byte(flash, flash->jedec_id, 0);
spi_addr_mode = 3;
}
}
}
/*
* Service routine to read status register until ready, or timeout occurs.
* Returns non-zero if error.
*/
int wait_till_ready(struct amb_norflash *flash)
{
unsigned long deadline;
int sr;
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
do {
if ((sr = read_sr(flash)) < 0)
break;
else if (!(sr & SR_WIP))
return 0;
cond_resched();
} while (!time_after_eq(jiffies, deadline));
return 1;
}
/*
* Erase the whole flash memory
*
* Returns 0 if successful, non-zero otherwise.
*/
static int erase_chip(struct amb_norflash *flash)
{
/* Wait until finished previous write command. */
if (wait_till_ready(flash))
return 1;
/* Send write enable, then erase commands. */
write_enable(flash);
/* Set up command buffer. */
flash->command[0] = OPCODE_CHIP_ERASE;
ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
return 0;
}
/*
* 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.
*/
static int erase_sector(struct amb_norflash *flash, u32 offset)
{
/* Wait until finished previous write command. */
if (wait_till_ready(flash))
return 1;
/* Send write enable, then erase commands. */
write_enable(flash);
/* Set up command buffer. */
flash->command[0] = flash->erase_opcode;
ambspi_send_cmd(flash, flash->command[0], 0, offset, flash->addr_width);
return 0;
}
/****************************************************************************/
/*
* MTD implementation
*/
/*
* 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.
*/
static int amba_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct amb_norflash *flash = mtd_to_amb(mtd);
u32 addr, len;
uint32_t rem;
div_u64_rem(instr->len, mtd->erasesize, &rem);
if (rem)
return -EINVAL;
addr = instr->addr;
len = instr->len;
mutex_lock(&flash->lock);
check_set_spinor_addr_mode(flash, 1);
/* whole-chip erase? */
if (len == flash->mtd.size) {
if (erase_chip(flash)) {
instr->state = MTD_ERASE_FAILED;
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return -EIO;
}
/* REVISIT in some cases we could speed up erasing large regions
* by using OPCODE_SE instead of OPCODE_BE_4K. We may have set up
* to use "small sector erase", but that's not always optimal.
*/
/* "sector"-at-a-time erase */
} else {
while (len) {
if (erase_sector(flash, addr)) {
instr->state = MTD_ERASE_FAILED;
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return -EIO;
}
addr += mtd->erasesize;
len -= mtd->erasesize;
}
}
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
/*
* Read an address range from the flash chip. The address range
* may be any size provided it is within the physical boundaries.
*/
static int amba_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct amb_norflash *flash = mtd_to_amb(mtd);
uint8_t opcode;
int offset;
int needread = 0;
int ret=0;
mutex_lock(&flash->lock);
/* Wait till previous write/erase is done. */
if (wait_till_ready(flash)) {
/* REVISIT status return?? */
mutex_unlock(&flash->lock);
return 1;
}
check_set_spinor_addr_mode(flash, 1);
/* Set up the write data buffer. */
opcode = flash->fast_read ? OPCODE_FAST_READ : OPCODE_NORM_READ;
flash->command[0] = opcode;
flash->dummy = 0;
for (offset = 0; offset < len; ) {
needread = len - offset;
if (needread >= AMBA_SPINOR_DMA_BUFF_SIZE) {
ret = ambspi_read_data(flash, from+offset, AMBA_SPINOR_DMA_BUFF_SIZE);
if(ret) {
dev_err((const struct device *)&flash->dev,
"SPI NOR read error from=%x len=%d\r\n", (u32)(from+offset), AMBA_SPINOR_DMA_BUFF_SIZE);
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return -1;
}
memcpy(buf+offset, flash->dmabuf, AMBA_SPINOR_DMA_BUFF_SIZE);
offset += AMBA_SPINOR_DMA_BUFF_SIZE;
}else{
ret = ambspi_read_data(flash, from+offset, needread);
if(ret) {
dev_err((const struct device *)&flash->dev,
"SPI NOR read error from=%x len=%d\r\n", (u32)(from+offset), needread);
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return -1;
}
memcpy(buf+offset, flash->dmabuf, needread);
offset += needread;
}
}
*retlen = offset;
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return 0;
}
/*
* Write an address range to the flash chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
* it is within the physical boundaries.
*/
static int amba_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct amb_norflash *flash = mtd_to_amb(mtd);
u32 page_offset, needwrite, needcopy, page_write;
mutex_lock(&flash->lock);
/* Wait until finished previous write command. */
if (wait_till_ready(flash)) {
mutex_unlock(&flash->lock);
return 1;
}
check_set_spinor_addr_mode(flash, 1);
write_enable(flash);
/* Set up the opcode in the write buffer. */
flash->command[0] = OPCODE_PP;
flash->dummy = 0;
page_offset = to & (flash->page_size - 1);
/* do all the bytes fit onto one page? */
if (page_offset + len <= flash->page_size) {
memcpy(flash->dmabuf, buf, len);
ambspi_prog_data(flash, 0, to, len);
*retlen = len;
} else {
u32 i;
u32 j;
/* the size of data remaining on the first page */
needwrite = flash->page_size - page_offset;
memcpy(flash->dmabuf, buf, needwrite);
ambspi_prog_data(flash, 0, to, needwrite);
*retlen = needwrite;
for (j=needwrite; j < len; j += needcopy) {
if (len-j < AMBA_SPINOR_DMA_BUFF_SIZE) {
needcopy = len-j;
}else{
needcopy = AMBA_SPINOR_DMA_BUFF_SIZE;
}
memcpy(flash->dmabuf, buf+j, needcopy);
for (i = 0; i < needcopy; i += page_write) {
page_write = needcopy - i;
if (page_write > flash->page_size) {
page_write = flash->page_size;
}
/* write the next page to flash */
if (wait_till_ready(flash)) {
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return 1;
}
write_enable(flash);
ambspi_prog_data(flash, i, to+i+j, page_write);
*retlen += page_write;
}
}
}
check_set_spinor_addr_mode(flash, 0);
mutex_unlock(&flash->lock);
return 0;
}
/****************************************************************************/
struct flash_info {
/* JEDEC id zero means "no ID" (most older chips);otherwise it has
* a high byte of zero plus three data bytes: the manufacturer id,
* then a two byte device id.
*/
u32 jedec_id;
u16 ext_id;
/* The size listed here is what works with OPCODE_SE, which isn't
* necessarily called a "sector" by the vendor.
*/
unsigned sector_size;
u16 n_sectors;
u16 page_size;
u16 addr_width;
u32 max_clk_hz;
u16 flags;
#define SECT_4K 0x01 /* OPCODE_BE_4K works uniformly */
};
#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _page_size, _max_clk_hz, _flags) \
{ \
.jedec_id = (_jedec_id), \
.ext_id = (_ext_id), \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = (_page_size), \
.max_clk_hz = (_max_clk_hz), \
.flags = (_flags), }
struct ambid_t {
char name[32];
struct flash_info driver_data;
};
static const struct ambid_t amb_ids[] = {
{ "s70fl01gs", INFO(0x010220, 0x4d00, 256 * 1024, 256, 512, 50000000, 0) },
{ "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, 256, 50000000, 0) },
{ "mx25l25645g", INFO(0xc22019, 0, 64 * 1024, 512, 256, 50000000, 0) },
{ "mx66l51235f", INFO(0xc2201a, 0, 64 * 1024, 1024, 256, 50000000, 0) },
{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, 256, 50000000, 0) },
{ "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, 256, 50000000, 0) },
{ "gd25q128", INFO(0xc84018, 0, 64 * 1024, 256, 256, 50000000, 0) },
{ "gd25q256c", INFO(0xc84019, 0, 64 * 1024, 512, 256, 50000000, 0) },
{ "gd25q512", INFO(0xc84020, 0, 64 * 1024, 1024, 256, 50000000, 0) },
{ },
};
static const struct ambid_t *jedec_probe(struct amb_norflash *flash)
{
int retval;
u8 code = OPCODE_RDID;
u8 id[5];
u32 jedec;
u16 ext_jedec;
struct flash_info *info;
int tmp;
/* JEDEC also defines an optional "extended device information"
* string for after vendor-specific data, after the three bytes
* we use here. Supporting some chips might require using it.
*/
retval = ambspi_read_reg(flash, 5, code, id);
if (retval < 0) {
dev_err((const struct device *)&flash->dev, "error %d reading id\n",
(int) retval);
return ERR_PTR(retval);
}
jedec = id[0];
jedec = jedec << 8;
jedec |= id[1];
jedec = jedec << 8;
jedec |= id[2];
ext_jedec = id[3] << 8 | id[4];
for (tmp = 0; tmp < ARRAY_SIZE(amb_ids) - 1; tmp++) {
info = (void *)&amb_ids[tmp].driver_data;
if (info->jedec_id == jedec) {
if (info->ext_id != 0 && info->ext_id != ext_jedec)
continue;
return &amb_ids[tmp];
}
}
dev_err((const struct device *)&flash->dev, "unrecognized JEDEC id %06x\n", jedec);
return ERR_PTR(-ENODEV);
}
static int amb_get_resource(struct amb_norflash *flash, struct platform_device *pdev)
{
struct resource *res;
int errorCode = 0;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No mem resource for spinor_reg!\n");
errorCode = -ENXIO;
goto spinor_get_resource_err_exit;
}
flash->regbase =
devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!flash->regbase) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
errorCode = -ENOMEM;
goto spinor_get_resource_err_exit;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(&pdev->dev, "No mem resource for spinor_reg!\n");
errorCode = -ENXIO;
goto spinor_get_resource_err_exit;
}
flash->dmaregbase =
devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!flash->dmaregbase) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
errorCode = -ENOMEM;
goto spinor_get_resource_err_exit;
}
return 0;
spinor_get_resource_err_exit:
return errorCode;
}
static int amb_spi_nor_probe(struct platform_device *pdev)
{
struct mtd_part_parser_data ppdata;
struct flash_platform_data data;
struct amb_norflash *flash;
struct flash_info *info;
unsigned i;
int errCode = 0;
const struct ambid_t *ambid = NULL;;
flash = kzalloc(sizeof(struct amb_norflash), GFP_KERNEL);
if (!flash) {
errCode = -ENOMEM;
goto amb_spi_nor_probe_exit;
}
mutex_init(&flash->lock);
platform_set_drvdata(pdev, flash);
flash->dev = &pdev->dev;
/* set 50Mhz as default spi clock */
flash->clk = 50000000;
amb_get_resource(flash, pdev);
ambspi_init(flash);
ambid = jedec_probe(flash);
if (IS_ERR(ambid))
return PTR_ERR(ambid);
else {
info = (void *)&ambid->driver_data;
}
data.type = kzalloc(sizeof(32), GFP_KERNEL);
strcpy(data.type, ambid->name);
flash->mtd.name = "amba_spinor";
flash->mtd.type = MTD_NORFLASH;
flash->mtd.writesize = 1;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.size = info->sector_size * info->n_sectors;
flash->mtd._erase = amba_erase;
flash->mtd._read = amba_read;
flash->mtd._write = amba_write;
flash->write_enable = write_enable;
flash->wait_till_ready = wait_till_ready;
/* prefer "small sector" erase if possible */
if (info->flags & SECT_4K) {
flash->erase_opcode = OPCODE_BE_4K;
flash->mtd.erasesize = 4096;
} else {
flash->erase_opcode = OPCODE_SE;
flash->mtd.erasesize = info->sector_size;
}
flash->mtd.flags |= MTD_NO_ERASE;
flash->page_size = info->page_size;
flash->mtd.writebufsize = flash->page_size;
flash->fast_read = false;
flash->command = kzalloc(5, GFP_KERNEL);
if(!flash->command) {
dev_err((const struct device *)&flash->dev,
"SPI NOR driver malloc command error\r\n");
errCode = -ENOMEM;
goto amb_spi_nor_probe_free_flash;
}
if (flash->page_size > AMBA_SPINOR_DMA_BUFF_SIZE) {
dev_err((const struct device *)&flash->dev,
"SPI NOR driver buff size should bigger than nor flash page size \r\n");
errCode = -EINVAL;
goto amb_spi_nor_probe_free_command;
}
flash->clk = info->max_clk_hz;
ambspi_init(flash);
if (info->addr_width)
flash->addr_width = info->addr_width;
else {
/* enable 4-byte addressing if the device exceeds 16MiB */
if (flash->mtd.size > 0x1000000) {
flash->addr_width = 4;
/* We have set 4-bytes mode in bootloader */
//set_4byte(flash, info->jedec_id, 1);
spi_addr_mode = 4;
} else {
flash->addr_width = 3;
spi_addr_mode = 3;
}
}
flash->jedec_id = info->jedec_id;
pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
flash->mtd.name,
(long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
flash->mtd.numeraseregions);
if (flash->mtd.numeraseregions)
for (i = 0; i < flash->mtd.numeraseregions; i++)
pr_debug("mtd.eraseregions[%d] = { .offset = 0x%llx, "
".erasesize = 0x%.8x (%uKiB), "
".numblocks = %d }\n",
i, (long long)flash->mtd.eraseregions[i].offset,
flash->mtd.eraseregions[i].erasesize,
flash->mtd.eraseregions[i].erasesize / 1024,
flash->mtd.eraseregions[i].numblocks);
ambspi_dma_config(flash);
ppdata.of_node = pdev->dev.of_node;
errCode = mtd_device_parse_register(&flash->mtd, NULL, &ppdata, NULL, 0);
if (errCode < 0)
goto amb_spi_nor_probe_free_command;
check_set_spinor_addr_mode(flash, 0);
printk("SPI NOR Controller probed\r\n");
return 0;
amb_spi_nor_probe_free_command:
kfree(flash->command);
amb_spi_nor_probe_free_flash:
kfree(flash);
amb_spi_nor_probe_exit:
return errCode;
}
static int amb_spi_nor_remove(struct platform_device *pdev)
{
struct amb_norflash *flash = platform_get_drvdata(pdev);
int status;
/* Clean up MTD stuff. */
status = mtd_device_unregister(&flash->mtd);
if (status == 0) {
kfree(flash->command);
dma_free_coherent(flash->dev,
AMBA_SPINOR_DMA_BUFF_SIZE,
flash->dmabuf, flash->dmaaddr);
kfree(flash);
}
return 0;
}
static void amb_spi_nor_shutdown(struct platform_device *pdev)
{
struct amb_norflash *flash = platform_get_drvdata(pdev);
/* Wait until finished previous write command. */
if (wait_till_ready(flash))
return;
/* Send write enable, then erase commands. */
//write_enable(flash);
switch (JEDEC_MFR(flash->jedec_id)) {
case CFI_MFR_MACRONIX:
case 0xEF: /* winbond */
case 0xC8: /* GD */
case CFI_MFR_ST: /*Micron*/
flash->command[0] = 0x66;
ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
flash->command[0] = 0x99;
ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
return;
default:
/* Spansion style */
/* Set up command buffer. */
/* FL01GS use 0xF0 as reset enable command */
flash->command[0] = 0xF0;
ambspi_send_cmd(flash, flash->command[0], 0, 0, 0);
}
}
#ifdef CONFIG_PM
static int amb_spi_nor_suspend(struct platform_device *pdev,
pm_message_t state)
{
int errorCode = 0;
struct amb_norflash *flash;
flash = platform_get_drvdata(pdev);
dev_dbg(&pdev->dev, "%s exit with %d @ %d\n",
__func__, errorCode, state.event);
return errorCode;
}
static int amb_spi_nor_resume(struct platform_device *pdev)
{
int errorCode = 0;
struct amb_norflash *flash;
flash = platform_get_drvdata(pdev);
ambspi_init(flash);
if (flash->addr_width == 4)
set_4byte(flash, flash->jedec_id, 1);
dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, errorCode);
return errorCode;
}
#endif
static const struct of_device_id ambarella_spi_nor_of_match[] = {
{.compatible = "ambarella,spinor", },
{},
};
MODULE_DEVICE_TABLE(of, ambarella_spi_nor_of_match);
static struct platform_driver amb_spi_nor_driver = {
.driver = {
.name = "ambarella-spinor",
.owner = THIS_MODULE,
.of_match_table = ambarella_spi_nor_of_match,
},
.probe = amb_spi_nor_probe,
.remove = amb_spi_nor_remove,
.shutdown = amb_spi_nor_shutdown,
#ifdef CONFIG_PM
.suspend = amb_spi_nor_suspend,
.resume = amb_spi_nor_resume,
#endif
/* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
};
module_platform_driver(amb_spi_nor_driver);
MODULE_AUTHOR("Johnson Diao");
MODULE_DESCRIPTION("Ambarella Media processor SPI NOR Flash Controller Driver");
MODULE_LICENSE("GPL");