FSMC是STA2x65的一个控制模块,全名Flexible Static Memory Controller (FSMC)
The Flexible Static Controller (FSMC) supports, with two chip selects
● ROM
● Static RAM
● NOR type flash memories, not multiplexed
● NOR type flash memories, multiplexed
It also supports, with two additional separate chip selects:
● NAND type flash memories, SLC small or large page
● NAND type flash memories, MLC
For NAND type of memories, the FSMC has been enhanced to implement an error correction in
hardware, based on the Bose-Chaudhuri-Hocquenghem (BCH) code, able to correct up to 8-bit
over 512 bytes+syndrome. The BCH code will calculate, in hardware, the syndrome only. The
actual correction will be implemented through S/W intervention.
FSMC 相关的驱动代码在 arch/arm/mach-cartesio/fsmc.c 中。
1. init/exit:
static int __init cartesio_fsmc_init(void)
{
return amba_driver_register(&cartesio_fsmc_driver);
}
static void __exit cartesio_fsmc_exit(void)
{
amba_driver_unregister(&cartesio_fsmc_driver);
}
arch_initcall(cartesio_fsmc_init);
module_exit(cartesio_fsmc_exit);
MODULE_AUTHOR("STMicroelectronics");
MODULE_DESCRIPTION("Flexible Static Memory Controller driver");
MODULE_LICENSE("GPL");
static struct amba_driver cartesio_fsmc_driver = {
.drv = {
.name = "fsmc",
.owner = THIS_MODULE,
},
.probe = cartesio_fsmc_probe,
.remove = cartesio_fsmc_remove,
.suspend = cartesio_fsmc_suspend,
.resume = cartesio_fsmc_resume,
.id_table = cartesio_fsmc_ids,
};
static struct amba_id cartesio_fsmc_ids[] = {
{
.id = 0x00080090,
.mask = 0x000FFFFF,
},
{ 0, 0 },
};
amba_driver:
struct amba_driver {
struct device_driver drv;
int (*probe)(struct amba_device *, struct amba_id *);
int (*remove)(struct amba_device *);
void (*shutdown)(struct amba_device *);
int (*suspend)(struct amba_device *, pm_message_t);
int (*resume)(struct amba_device *);
struct amba_id *id_table;
};
amba_id:
struct amba_id {
unsigned int id;
unsigned int mask;
void *data;
};
2. cartesio_fsmc_probe():
static int cartesio_fsmc_probe(struct amba_device *dev, struct amba_id *id)
{
struct fsmc_platform_data *plat = dev->dev.platform_data;
struct cartesio_fsmc_info *info;
int i, ret;
/* must have platform data */
if (!plat) {
dev_err(&dev->dev, "missing platform data\n");
return -EINVAL;
}
ret = amba_request_regions(dev, NULL);
if (ret < 0)
return -EBUSY;
info = kzalloc(sizeof(struct cartesio_fsmc_info), GFP_KERNEL);
if (info == NULL) {
dev_err(&dev->dev, "%s, not enough memory\n", __func__);
amba_release_regions(dev);
return -ENOMEM;
}
amba_set_drvdata(dev, info);
/* initialize fsmc_info */
info->base = ioremap(dev->res.start, dev->res.end - dev->res.start + 1);
for (i = 0; i < FSMC_BANK_NUM; i++) {
writel(plat->bank[i].control, info->base + FSMC_BCR(i));
writel(plat->bank[i].timing, info->base + FSMC_BTR(i));
}
for (i = 0; i < FSMC_DEVICE_NUM; i++) {
writel(plat->device[i].control, info->base + FSMC_PCR(i));
writel(plat->device[i].cm_timing, info->base + FSMC_PMEM(i));
writel(plat->device[i].am_timing, info->base + FSMC_PATT(i));
writel(plat->device[i].io_timing, info->base + FSMC_PIO(i));
}
dev_info(&dev->dev, "started at 0x%p\n", info->base);
return 0;
}
fsmc_platform_data:
/*
* Interface for FSMC driver configuration
*/
struct fsmc_platform_data {
struct bank_cfg bank[FSMC_BANK_NUM];
struct device_cfg device[FSMC_DEVICE_NUM];
};
/*
* Number of SRAM/NOR-Flash banks supported by FSMC
*/
#define FSMC_BANK_NUM 3
/*
* Number of PC-Card/NAND-Flash devices supported by FSMC
*/
#define FSMC_DEVICE_NUM 2
bank_cfg:
/*
* SRAM/NOR-Flash bank configuration
*/
struct bank_cfg {
u32 control;
u32 timing;
};
device_cfg:
/*
* PC-Card/NAND-Flash device configuration
*/
struct device_cfg {
u32 control;
u32 cm_timing; /* Common Memory Space Timing */
u32 am_timing; /* Attribute Memory Space Timing */
u32 io_timing; /* I/O Space Timing */
};
cartesio_fsmc_info:
/*
* FSMC driver context
*/
struct cartesio_fsmc_info {
void __iomem *base;
struct bank_cfg bank[FSMC_BANK_NUM];
struct device_cfg device[FSMC_DEVICE_NUM];
};
3. cartesio_fsmc_remove:
static int cartesio_fsmc_remove(struct amba_device *dev)
{
struct cartesio_fsmc_info *info;
info = amba_get_drvdata(dev);
iounmap(info->base);
amba_release_regions(dev);
kfree(info);
dev_info(&dev->dev, "module stopped and unloaded\n");
return 0;
}
4. PM: suspend/resume:
#ifdef CONFIG_PM
static int cartesio_fsmc_suspend(struct amba_device *dev, pm_message_t state)
{
struct cartesio_fsmc_info *info;
int i;
info = amba_get_drvdata(dev);
for (i = 0; i < FSMC_BANK_NUM; i++) {
info->bank[i].control = readl(info->base + FSMC_BCR(i));
info->bank[i].timing = readl(info->base + FSMC_BTR(i));
}
for (i = 0; i < FSMC_DEVICE_NUM; i++) {
info->device[i].control = readl(info->base + FSMC_PCR(i));
info->device[i].cm_timing = readl(info->base + FSMC_PMEM(i));
info->device[i].am_timing = readl(info->base + FSMC_PATT(i));
info->device[i].io_timing = readl(info->base + FSMC_PIO(i));
}
return 0;
}
static int cartesio_fsmc_resume(struct amba_device *dev)
{
struct cartesio_fsmc_info *info;
int i;
info = amba_get_drvdata(dev);
for (i = 0; i < FSMC_BANK_NUM; i++) {
writel(info->bank[i].control, info->base + FSMC_BCR(i));
writel(info->bank[i].timing , info->base + FSMC_BTR(i));
}
for (i = 0; i < FSMC_DEVICE_NUM; i++) {
writel(info->device[i].control, info->base + FSMC_PCR(i));
writel(info->device[i].cm_timing, info->base + FSMC_PMEM(i));
writel(info->device[i].am_timing, info->base + FSMC_PATT(i));
writel(info->device[i].io_timing, info->base + FSMC_PIO(i));
}
return 0;
}
#else
#define cartesio_fsmc_suspend NULL
#define cartesio_fsmc_resume NULL
#endif
5. other functions:
fsmc_device_enable:
void fsmc_device_enable(struct device *dev, int device)
{
struct cartesio_fsmc_info *info;
struct amba_device *adev;
u32 val;
if (device >= FSMC_DEVICE_NUM) {
dev_err(dev, "device ID is out of range: %d\n", device);
return;
}
adev = container_of(dev, struct amba_device, dev);
info = amba_get_drvdata(adev);
val = readl(info->base + FSMC_PCR(device));
val |= FSMC_PBKEN;
writel(val, info->base + FSMC_PCR(device));
}
EXPORT_SYMBOL(fsmc_device_enable);
fsmc_set_ecc:
void fsmc_set_ecc(struct device *dev, int device, enum ecc_state state)
{
#ifdef CONFIG_BCH_8
struct cartesio_fsmc_info *info;
struct amba_device *adev;
u32 val;
if (device >= FSMC_DEVICE_NUM) {
dev_err(dev, "device ID is out of range: %d\n", device);
return;
}
adev = container_of(dev, struct amba_device, dev);
info = amba_get_drvdata(adev);
val = readl(info->base + FSMC_PCR(device));
switch (state) {
case ECC_PAGE_512:
val &= ~FSMC_PECCP_256;
break;
case ECC_PAGE_256:
val |= FSMC_PECCP_256;
break;
case ECC_RESET:
/* disable hardware ECC and then re-enable to reset */
val &= ~FSMC_PECCEN;
writel(val, info->base + FSMC_PCR(device));
break;
}
val |= FSMC_PECCEN;
writel(val, info->base + FSMC_PCR(device));
#endif
}
EXPORT_SYMBOL(fsmc_set_ecc);
fsmc_get_parity:
int fsmc_get_parity(struct device *dev, int device, uint8_t *ecc_code)
{
struct cartesio_fsmc_info *info;
struct amba_device *adev;
u32 val;
if (device >= FSMC_DEVICE_NUM) {
dev_err(dev, "device ID out of range: %d\n", device);
return -ENODEV;
}
adev = container_of(dev, struct amba_device, dev);
info = amba_get_drvdata(adev);
#ifdef CONFIG_BCH_8
do {
val = readl(info->base + FSMC_STATUS(device));
} while (!(val & STATUS_ECCRDY));
val = readl(info->base + FSMC_ECCR(device));
ecc_code[0] = (val >> 0) & 0xFF;
ecc_code[1] = (val >> 8) & 0xFF;
ecc_code[2] = (val >> 16) & 0xFF;
ecc_code[3] = (val >> 24) & 0xFF;
val = readl(info->base + FSMC_ECC2R(device));
ecc_code[4] = (val >> 0) & 0xFF;
ecc_code[5] = (val >> 8) & 0xFF;
ecc_code[6] = (val >> 16) & 0xFF;
ecc_code[7] = (val >> 24) & 0xFF;
val = readl(info->base + FSMC_ECC3R(device));
ecc_code[8] = (val >> 0) & 0xFF;
ecc_code[9] = (val >> 8) & 0xFF;
ecc_code[10] = (val >> 16) & 0xFF;
ecc_code[11] = (val >> 24) & 0xFF;
val = readl(info->base + FSMC_STATUS(device));
if (!(val & 0x4000)) {
ecc_code[12] = (val >> 8) & 0xFF;
ecc_code[13] = (val >> 16) & 0xFF;
} else {
ecc_code[12] = (val >> 16) & 0xFF;
ecc_code[13] = 0x0;
}
val = readl(info->base + FSMC_STATUS(device));
val &= ~0x8000;
writel(val, info->base + FSMC_STATUS(device));
#else
val = readl(info->base + FSMC_ECCR(device));
ecc_code[0] = (val >> 0) & 0xFF;
ecc_code[1] = (val >> 8) & 0xFF;
ecc_code[2] = (val >> 16) & 0xFF;
#endif
return 0;
}
EXPORT_SYMBOL(fsmc_get_parity);