forked from Minki/linux
bc157b7596
This patchset adds the necessary drivers and infrastructure to access the external flash on the ATSTK1000 board through the MTD subsystem. With this stuff in place, it will be possible to use a jffs2 filesystem stored in the external flash as a root filesystem. It might also be possible to update the boot loader if you drop the write protection of partition 0. As suggested by David Woodhouse, I reworked the patches to use the physmap driver instead of introducing a separate mapping driver for the ATSTK1000. I've also cleaned up the hsmc header by removing useless comments and converting spaces to tabs (my headerfile generator needs some work.) Unfortunately, I couldn't unlock the flash in fixup_use_atmel_lock because the erase regions hadn't been set up yet, so I had to do it from cfi_amdstd_setup instead. This patch: This adds a simple API for configuring the static memory controller along with an implementation for the Atmel HSMC. Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com> Cc: David Woodhouse <dwmw2@infradead.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
165 lines
3.7 KiB
C
165 lines
3.7 KiB
C
/*
|
|
* Static Memory Controller for AT32 chips
|
|
*
|
|
* Copyright (C) 2006 Atmel Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#define DEBUG
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/arch/smc.h>
|
|
|
|
#include "hsmc.h"
|
|
|
|
#define NR_CHIP_SELECTS 6
|
|
|
|
struct hsmc {
|
|
void __iomem *regs;
|
|
struct clk *pclk;
|
|
struct clk *mck;
|
|
};
|
|
|
|
static struct hsmc *hsmc;
|
|
|
|
int smc_set_configuration(int cs, const struct smc_config *config)
|
|
{
|
|
unsigned long mul;
|
|
unsigned long offset;
|
|
u32 setup, pulse, cycle, mode;
|
|
|
|
if (!hsmc)
|
|
return -ENODEV;
|
|
if (cs >= NR_CHIP_SELECTS)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* cycles = x / T = x * f
|
|
* = ((x * 1000000000) * ((f * 65536) / 1000000000)) / 65536
|
|
* = ((x * 1000000000) * (((f / 10000) * 65536) / 100000)) / 65536
|
|
*/
|
|
mul = (clk_get_rate(hsmc->mck) / 10000) << 16;
|
|
mul /= 100000;
|
|
|
|
#define ns2cyc(x) ((((x) * mul) + 65535) >> 16)
|
|
|
|
setup = (HSMC_BF(NWE_SETUP, ns2cyc(config->nwe_setup))
|
|
| HSMC_BF(NCS_WR_SETUP, ns2cyc(config->ncs_write_setup))
|
|
| HSMC_BF(NRD_SETUP, ns2cyc(config->nrd_setup))
|
|
| HSMC_BF(NCS_RD_SETUP, ns2cyc(config->ncs_read_setup)));
|
|
pulse = (HSMC_BF(NWE_PULSE, ns2cyc(config->nwe_pulse))
|
|
| HSMC_BF(NCS_WR_PULSE, ns2cyc(config->ncs_write_pulse))
|
|
| HSMC_BF(NRD_PULSE, ns2cyc(config->nrd_pulse))
|
|
| HSMC_BF(NCS_RD_PULSE, ns2cyc(config->ncs_read_pulse)));
|
|
cycle = (HSMC_BF(NWE_CYCLE, ns2cyc(config->write_cycle))
|
|
| HSMC_BF(NRD_CYCLE, ns2cyc(config->read_cycle)));
|
|
|
|
switch (config->bus_width) {
|
|
case 1:
|
|
mode = HSMC_BF(DBW, HSMC_DBW_8_BITS);
|
|
break;
|
|
case 2:
|
|
mode = HSMC_BF(DBW, HSMC_DBW_16_BITS);
|
|
break;
|
|
case 4:
|
|
mode = HSMC_BF(DBW, HSMC_DBW_32_BITS);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (config->nrd_controlled)
|
|
mode |= HSMC_BIT(READ_MODE);
|
|
if (config->nwe_controlled)
|
|
mode |= HSMC_BIT(WRITE_MODE);
|
|
if (config->byte_write)
|
|
mode |= HSMC_BIT(BAT);
|
|
|
|
pr_debug("smc cs%d: setup/%08x pulse/%08x cycle/%08x mode/%08x\n",
|
|
cs, setup, pulse, cycle, mode);
|
|
|
|
offset = cs * 0x10;
|
|
hsmc_writel(hsmc, SETUP0 + offset, setup);
|
|
hsmc_writel(hsmc, PULSE0 + offset, pulse);
|
|
hsmc_writel(hsmc, CYCLE0 + offset, cycle);
|
|
hsmc_writel(hsmc, MODE0 + offset, mode);
|
|
hsmc_readl(hsmc, MODE0); /* I/O barrier */
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(smc_set_configuration);
|
|
|
|
static int hsmc_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *regs;
|
|
struct clk *pclk, *mck;
|
|
int ret;
|
|
|
|
if (hsmc)
|
|
return -EBUSY;
|
|
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!regs)
|
|
return -ENXIO;
|
|
pclk = clk_get(&pdev->dev, "pclk");
|
|
if (IS_ERR(pclk))
|
|
return PTR_ERR(pclk);
|
|
mck = clk_get(&pdev->dev, "mck");
|
|
if (IS_ERR(mck)) {
|
|
ret = PTR_ERR(mck);
|
|
goto out_put_pclk;
|
|
}
|
|
|
|
ret = -ENOMEM;
|
|
hsmc = kzalloc(sizeof(struct hsmc), GFP_KERNEL);
|
|
if (!hsmc)
|
|
goto out_put_clocks;
|
|
|
|
clk_enable(pclk);
|
|
clk_enable(mck);
|
|
|
|
hsmc->pclk = pclk;
|
|
hsmc->mck = mck;
|
|
hsmc->regs = ioremap(regs->start, regs->end - regs->start + 1);
|
|
if (!hsmc->regs)
|
|
goto out_disable_clocks;
|
|
|
|
dev_info(&pdev->dev, "Atmel Static Memory Controller at 0x%08lx\n",
|
|
(unsigned long)regs->start);
|
|
|
|
platform_set_drvdata(pdev, hsmc);
|
|
|
|
return 0;
|
|
|
|
out_disable_clocks:
|
|
clk_disable(mck);
|
|
clk_disable(pclk);
|
|
kfree(hsmc);
|
|
out_put_clocks:
|
|
clk_put(mck);
|
|
out_put_pclk:
|
|
clk_put(pclk);
|
|
hsmc = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static struct platform_driver hsmc_driver = {
|
|
.probe = hsmc_probe,
|
|
.driver = {
|
|
.name = "smc",
|
|
},
|
|
};
|
|
|
|
static int __init hsmc_init(void)
|
|
{
|
|
return platform_driver_register(&hsmc_driver);
|
|
}
|
|
arch_initcall(hsmc_init);
|