435 lines
11 KiB
C
435 lines
11 KiB
C
/*
|
|
* Copyright (C) 2007 STMicroelectronics Limited
|
|
* Author: Stuart Menefy <stuart.menefy@st.com>
|
|
*
|
|
* May be copied or modified under the terms of the GNU General Public
|
|
* License. See linux/COPYING for more information.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/sysdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/stm/emi.h>
|
|
#include <linux/stm/device.h>
|
|
#include <linux/stm/clk.h>
|
|
|
|
|
|
#define EMI_GEN_CFG 0x0028
|
|
#define EMI_BANKNUMBER 0x0860
|
|
#define EMI_BANK_ENABLE 0x0280
|
|
#define BANK_BASEADDRESS(b) (0x800 + (0x10 * b))
|
|
#define BANK_EMICONFIGDATA(b, r) (0x100 + (0x40 * b) + (8 * r))
|
|
#define EMI_COMMON_CFG(reg) (0x10 + (0x8 * (reg)))
|
|
|
|
|
|
static struct platform_device *emi;
|
|
static struct clk *emi_clk;
|
|
#define emi_initialised (emi != NULL)
|
|
static unsigned long emi_memory_base;
|
|
static void __iomem *emi_control;
|
|
static struct stm_device_state *emi_device_state;
|
|
|
|
static inline void emi_clk_xxable(int enable)
|
|
{
|
|
if (!emi_clk || IS_ERR(emi_clk))
|
|
return;
|
|
|
|
if (enable)
|
|
clk_enable(emi_clk);
|
|
else
|
|
clk_disable(emi_clk);
|
|
}
|
|
|
|
static inline void emi_clk_enable(void)
|
|
{
|
|
emi_clk_xxable(1);
|
|
}
|
|
|
|
static inline void emi_clk_disable(void)
|
|
{
|
|
emi_clk_xxable(0);
|
|
}
|
|
|
|
|
|
unsigned long emi_bank_base(int bank)
|
|
{
|
|
unsigned long reg;
|
|
|
|
BUG_ON(bank < 0 || bank >= EMI_BANKS);
|
|
BUG_ON(!emi_initialised);
|
|
|
|
reg = readl(emi_control + BANK_BASEADDRESS(bank));
|
|
|
|
return emi_memory_base + (reg << 22);
|
|
}
|
|
EXPORT_SYMBOL_GPL(emi_bank_base);
|
|
|
|
void emi_bank_configure(int bank, unsigned long data[4])
|
|
{
|
|
int i;
|
|
|
|
BUG_ON(bank < 0 || bank >= EMI_BANKS);
|
|
BUG_ON(!emi_initialised);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
writel(data[i], emi_control + BANK_EMICONFIGDATA(bank, i));
|
|
}
|
|
EXPORT_SYMBOL_GPL(emi_bank_configure);
|
|
|
|
void emi_config_pcmode(int bank, int pc_mode)
|
|
{
|
|
int mask;
|
|
|
|
BUG_ON(!emi_initialised);
|
|
|
|
switch (bank) {
|
|
case 2: /* Bank C */
|
|
mask = 1<<3;
|
|
break;
|
|
case 3: /* Bank D */
|
|
mask = 1<<4;
|
|
break;
|
|
default:
|
|
mask = 0;
|
|
break;
|
|
}
|
|
|
|
if (mask) {
|
|
u32 val = readl(emi_control + EMI_GEN_CFG);
|
|
if (pc_mode)
|
|
val |= mask;
|
|
else
|
|
val &= (~mask);
|
|
writel(val, emi_control + EMI_GEN_CFG);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(emi_config_pcmode);
|
|
|
|
/*
|
|
* ______________________________
|
|
* EMIADDR ___/ \________
|
|
* \______________________________/
|
|
*
|
|
* (The cycle time specified in nano seconds)
|
|
*
|
|
* |-----------------------------| cycle_time
|
|
* ______________ ___________
|
|
* CYCLE_TIME / \______________/
|
|
*
|
|
*
|
|
* (IORD_start the number of nano seconds after the start of the cycle the
|
|
* RD strobe is asserted IORD_end the number of nano seconds before the
|
|
* end of the cycle the RD strobe is de-asserted.)
|
|
* _______________________
|
|
* IORD ______/ \________
|
|
*
|
|
* |--| |---|
|
|
* ^--- IORD_start ^----- IORD_end
|
|
*
|
|
* (RD_latch the number of nano seconds at the end of the cycle the read
|
|
* data is latched)
|
|
* __
|
|
* RD_LATCH ______________________/__\________
|
|
*
|
|
* |------------|
|
|
* ^---------- RD_latch
|
|
*
|
|
* (IOWR_start the number of nano seconds after the start of the cycle the
|
|
* WR strobe is asserted IOWR_end the number of nano seconds before the
|
|
* end of the cycle the WR strobe is de-asserted.)
|
|
* _______________________
|
|
* IOWR ______/ \________
|
|
*
|
|
* |--| |---|
|
|
* ^--- IOWR_start ^----- IOWR_end
|
|
*/
|
|
|
|
|
|
|
|
/* NOTE: these calculations assume a 100MHZ clock */
|
|
|
|
|
|
|
|
static void __init set_pata_read_timings(int bank, int cycle_time,
|
|
int IORD_start, int IORD_end, int RD_latch)
|
|
{
|
|
cycle_time = cycle_time / 10;
|
|
IORD_start = IORD_start / 5;
|
|
IORD_end = IORD_end / 5;
|
|
RD_latch = RD_latch / 10;
|
|
|
|
writel((cycle_time << 24) | (IORD_start << 8) | (IORD_end << 12),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 1));
|
|
writel(0x791 | (RD_latch << 20),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 0));
|
|
}
|
|
|
|
static void __init set_pata_write_timings(int bank, int cycle_time,
|
|
int IOWR_start, int IOWR_end)
|
|
{
|
|
cycle_time = cycle_time / 10;
|
|
IOWR_start = IOWR_start / 5;
|
|
IOWR_end = IOWR_end / 5;
|
|
|
|
writel((cycle_time << 24) | (IOWR_start << 8) | (IOWR_end << 12),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 2));
|
|
}
|
|
|
|
void __init emi_config_pata(int bank, int pc_mode)
|
|
{
|
|
BUG_ON(bank < 0 || bank >= EMI_BANKS);
|
|
BUG_ON(!emi_initialised);
|
|
|
|
/* Set timings for PIO4 */
|
|
set_pata_read_timings(bank, 120, 35, 30, 20);
|
|
set_pata_write_timings(bank, 120, 35, 30);
|
|
|
|
emi_config_pcmode(bank, pc_mode);
|
|
|
|
}
|
|
|
|
static void set_nand_read_timings(int bank, int cycle_time,
|
|
int IORD_start, int IORD_end,
|
|
int RD_latch, int busreleasetime,
|
|
int wait_active_low )
|
|
{
|
|
cycle_time = cycle_time / 10; /* cycles */
|
|
IORD_start = IORD_start / 5; /* phases */
|
|
IORD_end = IORD_end / 5; /* phases */
|
|
RD_latch = RD_latch / 10; /* cycles */
|
|
busreleasetime = busreleasetime / 10; /* cycles */
|
|
|
|
writel(0x04000699 | (busreleasetime << 11) | (RD_latch << 20) | (wait_active_low << 25),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 0));
|
|
|
|
writel((cycle_time << 24) | (IORD_start << 12) | (IORD_end << 8),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 1));
|
|
}
|
|
|
|
static void set_nand_write_timings(int bank, int cycle_time,
|
|
int IOWR_start, int IOWR_end)
|
|
{
|
|
cycle_time = cycle_time / 10; /* cycles */
|
|
IOWR_start = IOWR_start / 5; /* phases */
|
|
IOWR_end = IOWR_end / 5; /* phases */
|
|
|
|
writel((cycle_time << 24) | (IOWR_start << 12) | (IOWR_end << 8),
|
|
emi_control + BANK_EMICONFIGDATA(bank, 2));
|
|
}
|
|
|
|
void emi_config_nand(int bank, struct emi_timing_data *timing_data)
|
|
{
|
|
BUG_ON(bank < 0 || bank >= EMI_BANKS);
|
|
BUG_ON(!emi_initialised);
|
|
|
|
set_nand_read_timings(bank,
|
|
timing_data->rd_cycle_time,
|
|
timing_data->rd_oee_start,
|
|
timing_data->rd_oee_end,
|
|
timing_data->rd_latchpoint,
|
|
timing_data->busreleasetime,
|
|
timing_data->wait_active_low);
|
|
|
|
set_nand_write_timings(bank,
|
|
timing_data->wr_cycle_time,
|
|
timing_data->wr_oee_start,
|
|
timing_data->wr_oee_end);
|
|
|
|
/* Disable PC mode */
|
|
emi_config_pcmode(bank, 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(emi_config_nand);
|
|
|
|
void emi_config_pci(void)
|
|
{
|
|
u32 tmp;
|
|
|
|
BUG_ON(!emi_initialised);
|
|
|
|
tmp = readl(emi_control + EMI_GEN_CFG);
|
|
/* bit 16 is undocumented but enables extra pullups on the bus which
|
|
* is needed for correction operation if the EMI is accessed
|
|
* simultaneously with PCI
|
|
*/
|
|
writel(tmp | (1 << 16), emi_control + EMI_GEN_CFG);
|
|
}
|
|
EXPORT_SYMBOL_GPL(emi_config_pci);
|
|
|
|
#ifdef CONFIG_PM
|
|
/*
|
|
* Note on Power Management of EMI device
|
|
* ======================================
|
|
* The EMI is registered twice on different view:
|
|
* 1. as platform_device to acquire the platform specific
|
|
* capability (via sysconf)
|
|
* 2. as sysdev_device to really manage the suspend/resume
|
|
* operation on standby and hibernation
|
|
*/
|
|
|
|
/*
|
|
* emi_num_common_cfg = 12 common config +
|
|
* emi_bank_enable(0x280) +
|
|
* emi_bank_number(0x860)
|
|
*/
|
|
#define emi_num_common_cfg (12 + 2)
|
|
#define emi_num_bank 5
|
|
#define emi_num_bank_cfg 4
|
|
|
|
struct emi_pm_bank {
|
|
unsigned long cfg[emi_num_bank_cfg];
|
|
unsigned long base_address;
|
|
};
|
|
|
|
struct emi_pm {
|
|
unsigned long common_cfg[emi_num_common_cfg];
|
|
struct emi_pm_bank bank[emi_num_bank];
|
|
};
|
|
|
|
static int emi_sysdev_suspend(struct sys_device *dev, pm_message_t state)
|
|
{
|
|
int idx;
|
|
int bank, data;
|
|
static struct emi_pm *emi_saved_data;
|
|
|
|
switch (state.event) {
|
|
case PM_EVENT_ON:
|
|
if (emi_saved_data) {
|
|
/* restore the previous common value */
|
|
for (idx = 0; idx < emi_num_common_cfg-2; ++idx)
|
|
writel(emi_saved_data->common_cfg[idx],
|
|
emi_control+EMI_COMMON_CFG(idx));
|
|
writel(emi_saved_data->common_cfg[12], emi_control
|
|
+ EMI_BANK_ENABLE);
|
|
writel(emi_saved_data->common_cfg[13], emi_control
|
|
+ EMI_BANKNUMBER);
|
|
/* restore the previous bank values */
|
|
for (bank = 0; bank < emi_num_bank; ++bank) {
|
|
writel(emi_saved_data->bank[bank].base_address,
|
|
emi_control + BANK_BASEADDRESS(bank));
|
|
for (data = 0; data < emi_num_bank_cfg; ++data)
|
|
emi_bank_configure(bank, emi_saved_data->bank[bank].cfg);
|
|
}
|
|
kfree(emi_saved_data);
|
|
emi_saved_data = NULL;
|
|
} else
|
|
emi_clk_enable();
|
|
stm_device_power(emi_device_state, stm_device_power_on);
|
|
break;
|
|
case PM_EVENT_SUSPEND:
|
|
stm_device_power(emi_device_state, stm_device_power_off);
|
|
emi_clk_disable();
|
|
break;
|
|
case PM_EVENT_FREEZE:
|
|
emi_saved_data = kmalloc(sizeof(struct emi_pm), GFP_NOWAIT);
|
|
if (!emi_saved_data) {
|
|
printk(KERN_ERR "Unable to freeze the emi registers\n");
|
|
return -ENOMEM;
|
|
}
|
|
/* save the emi common values */
|
|
for (idx = 0; idx < emi_num_common_cfg-2; ++idx)
|
|
emi_saved_data->common_cfg[idx] =
|
|
readl(emi_control + EMI_COMMON_CFG(idx));
|
|
emi_saved_data->common_cfg[12] =
|
|
readl(emi_control + EMI_BANK_ENABLE);
|
|
emi_saved_data->common_cfg[13] =
|
|
readl(emi_control + EMI_BANKNUMBER);
|
|
/* save the emi bank value */
|
|
for (bank = 0; bank < emi_num_bank; ++bank) {
|
|
emi_saved_data->bank[bank].base_address =
|
|
readl(emi_control + BANK_BASEADDRESS(bank));
|
|
for (data = 0; data < emi_num_bank_cfg; ++data)
|
|
emi_saved_data->bank[bank].cfg[data] =
|
|
readl(emi_control + BANK_EMICONFIGDATA(bank, data));
|
|
}
|
|
/* on hibernation don't turn-off emi for harddisk issue */
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int emi_sysdev_resume(struct sys_device *dev)
|
|
{
|
|
return emi_sysdev_suspend(dev, PMSG_ON);
|
|
}
|
|
|
|
static struct sysdev_class emi_sysdev_class = {
|
|
.name = "emi",
|
|
.suspend = emi_sysdev_suspend,
|
|
.resume = emi_sysdev_resume,
|
|
};
|
|
|
|
struct sys_device emi_sysdev_dev = {
|
|
.id = 0,
|
|
.cls = &emi_sysdev_class,
|
|
};
|
|
|
|
static int __init emi_sysdev_register(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = sysdev_class_register(&emi_sysdev_class);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = sysdev_register(&emi_sysdev_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define emi_sysdev_register()
|
|
#endif
|
|
|
|
static int __init emi_driver_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
|
|
BUG_ON(emi_initialised);
|
|
|
|
emi_device_state = devm_stm_device_init(&pdev->dev,
|
|
(struct stm_device_config *)pdev->dev.platform_data);
|
|
|
|
if (!emi_device_state)
|
|
return -EBUSY;
|
|
|
|
/* acquires control base resource */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
|
|
if (!request_mem_region(res->start, res->end - res->start, "EMI"))
|
|
return -EBUSY;
|
|
|
|
emi_control = ioremap(res->start, res->end - res->start);
|
|
if (emi_control == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* acquires mem base resource */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
emi_memory_base = res->start;
|
|
|
|
emi_clk = clk_get(&pdev->dev, "emi_clk");
|
|
if (!emi_clk)
|
|
pr_warning("emi_clk not found!\n");
|
|
|
|
emi_clk_enable();
|
|
emi = pdev; /* to say the EMI is initialised */
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver emi_driver = {
|
|
.driver.name = "emi",
|
|
.driver.owner = THIS_MODULE,
|
|
.probe = emi_driver_probe,
|
|
};
|
|
|
|
static int __init stm_emi_driver_init(void)
|
|
{
|
|
emi_sysdev_register();
|
|
return platform_driver_register(&emi_driver);
|
|
}
|
|
|
|
postcore_initcall(stm_emi_driver_init);
|