274 lines
7.5 KiB
C
274 lines
7.5 KiB
C
/*======================================================================
|
|
|
|
Device driver for the PCMCIA control functionality of StrongARM
|
|
SA-1100 microprocessors.
|
|
|
|
The contents of this file are subject to the Mozilla Public
|
|
License Version 1.1 (the "License"); you may not use this file
|
|
except in compliance with the License. You may obtain a copy of
|
|
the License at http://www.mozilla.org/MPL/
|
|
|
|
Software distributed under the License is distributed on an "AS
|
|
IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
implied. See the License for the specific language governing
|
|
rights and limitations under the License.
|
|
|
|
The initial developer of the original code is John G. Dorsey
|
|
<john+@cs.cmu.edu>. Portions created by John G. Dorsey are
|
|
Copyright (C) 1999 John G. Dorsey. All Rights Reserved.
|
|
|
|
Alternatively, the contents of this file may be used under the
|
|
terms of the GNU Public License version 2 (the "GPL"), in which
|
|
case the provisions of the GPL are applicable instead of the
|
|
above. If you wish to allow the use of your version of this file
|
|
only under the terms of the GPL and not to allow others to use
|
|
your version of this file under the MPL, indicate your decision
|
|
by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this
|
|
file under either the MPL or the GPL.
|
|
|
|
======================================================================*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <mach/hardware.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/system.h>
|
|
|
|
#include "soc_common.h"
|
|
#include "sa11xx_base.h"
|
|
|
|
|
|
/*
|
|
* sa1100_pcmcia_default_mecr_timing
|
|
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
*
|
|
* Calculate MECR clock wait states for given CPU clock
|
|
* speed and command wait state. This function can be over-
|
|
* written by a board specific version.
|
|
*
|
|
* The default is to simply calculate the BS values as specified in
|
|
* the INTEL SA1100 development manual
|
|
* "Expansion Memory (PCMCIA) Configuration Register (MECR)"
|
|
* that's section 10.2.5 in _my_ version of the manual ;)
|
|
*/
|
|
static unsigned int
|
|
sa1100_pcmcia_default_mecr_timing(struct soc_pcmcia_socket *skt,
|
|
unsigned int cpu_speed,
|
|
unsigned int cmd_time)
|
|
{
|
|
return sa1100_pcmcia_mecr_bs(cmd_time, cpu_speed);
|
|
}
|
|
|
|
/* sa1100_pcmcia_set_mecr()
|
|
* ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
*
|
|
* set MECR value for socket <sock> based on this sockets
|
|
* io, mem and attribute space access speed.
|
|
* Call board specific BS value calculation to allow boards
|
|
* to tweak the BS values.
|
|
*/
|
|
static int
|
|
sa1100_pcmcia_set_mecr(struct soc_pcmcia_socket *skt, unsigned int cpu_clock)
|
|
{
|
|
struct soc_pcmcia_timing timing;
|
|
u32 mecr, old_mecr;
|
|
unsigned long flags;
|
|
unsigned int bs_io, bs_mem, bs_attr;
|
|
|
|
soc_common_pcmcia_get_timing(skt, &timing);
|
|
|
|
bs_io = skt->ops->get_timing(skt, cpu_clock, timing.io);
|
|
bs_mem = skt->ops->get_timing(skt, cpu_clock, timing.mem);
|
|
bs_attr = skt->ops->get_timing(skt, cpu_clock, timing.attr);
|
|
|
|
local_irq_save(flags);
|
|
|
|
old_mecr = mecr = MECR;
|
|
MECR_FAST_SET(mecr, skt->nr, 0);
|
|
MECR_BSIO_SET(mecr, skt->nr, bs_io);
|
|
MECR_BSA_SET(mecr, skt->nr, bs_attr);
|
|
MECR_BSM_SET(mecr, skt->nr, bs_mem);
|
|
if (old_mecr != mecr)
|
|
MECR = mecr;
|
|
|
|
local_irq_restore(flags);
|
|
|
|
debug(skt, 2, "FAST %X BSM %X BSA %X BSIO %X\n",
|
|
MECR_FAST_GET(mecr, skt->nr),
|
|
MECR_BSM_GET(mecr, skt->nr), MECR_BSA_GET(mecr, skt->nr),
|
|
MECR_BSIO_GET(mecr, skt->nr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_FREQ
|
|
static int
|
|
sa1100_pcmcia_frequency_change(struct soc_pcmcia_socket *skt,
|
|
unsigned long val,
|
|
struct cpufreq_freqs *freqs)
|
|
{
|
|
switch (val) {
|
|
case CPUFREQ_PRECHANGE:
|
|
if (freqs->new > freqs->old)
|
|
sa1100_pcmcia_set_mecr(skt, freqs->new);
|
|
break;
|
|
|
|
case CPUFREQ_POSTCHANGE:
|
|
if (freqs->new < freqs->old)
|
|
sa1100_pcmcia_set_mecr(skt, freqs->new);
|
|
break;
|
|
case CPUFREQ_RESUMECHANGE:
|
|
sa1100_pcmcia_set_mecr(skt, freqs->new);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int
|
|
sa1100_pcmcia_set_timing(struct soc_pcmcia_socket *skt)
|
|
{
|
|
return sa1100_pcmcia_set_mecr(skt, cpufreq_get(0));
|
|
}
|
|
|
|
static int
|
|
sa1100_pcmcia_show_timing(struct soc_pcmcia_socket *skt, char *buf)
|
|
{
|
|
struct soc_pcmcia_timing timing;
|
|
unsigned int clock = cpufreq_get(0);
|
|
unsigned long mecr = MECR;
|
|
char *p = buf;
|
|
|
|
soc_common_pcmcia_get_timing(skt, &timing);
|
|
|
|
p+=sprintf(p, "I/O : %u (%u)\n", timing.io,
|
|
sa1100_pcmcia_cmd_time(clock, MECR_BSIO_GET(mecr, skt->nr)));
|
|
|
|
p+=sprintf(p, "attribute: %u (%u)\n", timing.attr,
|
|
sa1100_pcmcia_cmd_time(clock, MECR_BSA_GET(mecr, skt->nr)));
|
|
|
|
p+=sprintf(p, "common : %u (%u)\n", timing.mem,
|
|
sa1100_pcmcia_cmd_time(clock, MECR_BSM_GET(mecr, skt->nr)));
|
|
|
|
return p - buf;
|
|
}
|
|
|
|
static const char *skt_names[] = {
|
|
"PCMCIA socket 0",
|
|
"PCMCIA socket 1",
|
|
};
|
|
|
|
#define SKT_DEV_INFO_SIZE(n) \
|
|
(sizeof(struct skt_dev_info) + (n)*sizeof(struct soc_pcmcia_socket))
|
|
|
|
int sa11xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt)
|
|
{
|
|
skt->res_skt.start = _PCMCIA(skt->nr);
|
|
skt->res_skt.end = _PCMCIA(skt->nr) + PCMCIASp - 1;
|
|
skt->res_skt.name = skt_names[skt->nr];
|
|
skt->res_skt.flags = IORESOURCE_MEM;
|
|
|
|
skt->res_io.start = _PCMCIAIO(skt->nr);
|
|
skt->res_io.end = _PCMCIAIO(skt->nr) + PCMCIAIOSp - 1;
|
|
skt->res_io.name = "io";
|
|
skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
|
|
|
|
skt->res_mem.start = _PCMCIAMem(skt->nr);
|
|
skt->res_mem.end = _PCMCIAMem(skt->nr) + PCMCIAMemSp - 1;
|
|
skt->res_mem.name = "memory";
|
|
skt->res_mem.flags = IORESOURCE_MEM;
|
|
|
|
skt->res_attr.start = _PCMCIAAttr(skt->nr);
|
|
skt->res_attr.end = _PCMCIAAttr(skt->nr) + PCMCIAAttrSp - 1;
|
|
skt->res_attr.name = "attribute";
|
|
skt->res_attr.flags = IORESOURCE_MEM;
|
|
|
|
return soc_pcmcia_add_one(skt);
|
|
}
|
|
EXPORT_SYMBOL(sa11xx_drv_pcmcia_add_one);
|
|
|
|
void sa11xx_drv_pcmcia_ops(struct pcmcia_low_level *ops)
|
|
{
|
|
/*
|
|
* set default MECR calculation if the board specific
|
|
* code did not specify one...
|
|
*/
|
|
if (!ops->get_timing)
|
|
ops->get_timing = sa1100_pcmcia_default_mecr_timing;
|
|
|
|
/* Provide our SA11x0 specific timing routines. */
|
|
ops->set_timing = sa1100_pcmcia_set_timing;
|
|
ops->show_timing = sa1100_pcmcia_show_timing;
|
|
#ifdef CONFIG_CPU_FREQ
|
|
ops->frequency_change = sa1100_pcmcia_frequency_change;
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(sa11xx_drv_pcmcia_ops);
|
|
|
|
int sa11xx_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops,
|
|
int first, int nr)
|
|
{
|
|
struct skt_dev_info *sinfo;
|
|
struct soc_pcmcia_socket *skt;
|
|
int i, ret = 0;
|
|
|
|
sa11xx_drv_pcmcia_ops(ops);
|
|
|
|
sinfo = kzalloc(SKT_DEV_INFO_SIZE(nr), GFP_KERNEL);
|
|
if (!sinfo)
|
|
return -ENOMEM;
|
|
|
|
sinfo->nskt = nr;
|
|
|
|
/* Initiliaze processor specific parameters */
|
|
for (i = 0; i < nr; i++) {
|
|
skt = &sinfo->skt[i];
|
|
|
|
skt->nr = first + i;
|
|
skt->ops = ops;
|
|
skt->socket.owner = ops->owner;
|
|
skt->socket.dev.parent = dev;
|
|
skt->socket.pci_irq = NO_IRQ;
|
|
|
|
ret = sa11xx_drv_pcmcia_add_one(skt);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
if (ret) {
|
|
while (--i >= 0)
|
|
soc_pcmcia_remove_one(&sinfo->skt[i]);
|
|
kfree(sinfo);
|
|
} else {
|
|
dev_set_drvdata(dev, sinfo);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sa11xx_drv_pcmcia_probe);
|
|
|
|
static int __init sa11xx_pcmcia_init(void)
|
|
{
|
|
return 0;
|
|
}
|
|
fs_initcall(sa11xx_pcmcia_init);
|
|
|
|
static void __exit sa11xx_pcmcia_exit(void) {}
|
|
|
|
module_exit(sa11xx_pcmcia_exit);
|
|
|
|
MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>");
|
|
MODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11xx core socket driver");
|
|
MODULE_LICENSE("Dual MPL/GPL");
|