9480e307cd
In PM v1, all devices were called at SUSPEND_DISABLE level. Then all devices were called at SUSPEND_SAVE_STATE level, and finally SUSPEND_POWER_DOWN level. However, with PM v2, to maintain compatibility for platform devices, I arranged for the PM v2 suspend/resume callbacks to call the old PM v1 suspend/resume callbacks three times with each level in order so that existing drivers continued to work. Since this is obsolete infrastructure which is no longer necessary, we can remove it. Here's an (untested) patch to do exactly that. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
242 lines
6.2 KiB
C
242 lines
6.2 KiB
C
/*======================================================================
|
|
|
|
Device driver for the PCMCIA control functionality of PXA2xx
|
|
microprocessors.
|
|
|
|
The contents of this file may be used under the
|
|
terms of the GNU Public License version 2 (the "GPL")
|
|
|
|
(c) Ian Molton (spyro@f2s.com) 2003
|
|
(c) Stefan Eletzhofer (stefan.eletzhofer@inquant.de) 2003,4
|
|
|
|
derived from sa11xx_base.c
|
|
|
|
Portions created by John G. Dorsey are
|
|
Copyright (C) 1999 John G. Dorsey.
|
|
|
|
======================================================================*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/config.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <asm/hardware.h>
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/system.h>
|
|
#include <asm/arch/pxa-regs.h>
|
|
|
|
#include <pcmcia/cs_types.h>
|
|
#include <pcmcia/ss.h>
|
|
#include <pcmcia/bulkmem.h>
|
|
#include <pcmcia/cistpl.h>
|
|
|
|
#include "cs_internal.h"
|
|
#include "soc_common.h"
|
|
#include "pxa2xx_base.h"
|
|
|
|
|
|
#define MCXX_SETUP_MASK (0x7f)
|
|
#define MCXX_ASST_MASK (0x1f)
|
|
#define MCXX_HOLD_MASK (0x3f)
|
|
#define MCXX_SETUP_SHIFT (0)
|
|
#define MCXX_ASST_SHIFT (7)
|
|
#define MCXX_HOLD_SHIFT (14)
|
|
|
|
static inline u_int pxa2xx_mcxx_hold(u_int pcmcia_cycle_ns,
|
|
u_int mem_clk_10khz)
|
|
{
|
|
u_int code = pcmcia_cycle_ns * mem_clk_10khz;
|
|
return (code / 300000) + ((code % 300000) ? 1 : 0) - 1;
|
|
}
|
|
|
|
static inline u_int pxa2xx_mcxx_asst(u_int pcmcia_cycle_ns,
|
|
u_int mem_clk_10khz)
|
|
{
|
|
u_int code = pcmcia_cycle_ns * mem_clk_10khz;
|
|
return (code / 300000) + ((code % 300000) ? 1 : 0) - 1;
|
|
}
|
|
|
|
static inline u_int pxa2xx_mcxx_setup(u_int pcmcia_cycle_ns,
|
|
u_int mem_clk_10khz)
|
|
{
|
|
u_int code = pcmcia_cycle_ns * mem_clk_10khz;
|
|
return (code / 100000) + ((code % 100000) ? 1 : 0) - 1;
|
|
}
|
|
|
|
/* This function returns the (approximate) command assertion period, in
|
|
* nanoseconds, for a given CPU clock frequency and MCXX_ASST value:
|
|
*/
|
|
static inline u_int pxa2xx_pcmcia_cmd_time(u_int mem_clk_10khz,
|
|
u_int pcmcia_mcxx_asst)
|
|
{
|
|
return (300000 * (pcmcia_mcxx_asst + 1) / mem_clk_10khz);
|
|
}
|
|
|
|
static int pxa2xx_pcmcia_set_mcmem( int sock, int speed, int clock )
|
|
{
|
|
MCMEM(sock) = ((pxa2xx_mcxx_setup(speed, clock)
|
|
& MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT)
|
|
| ((pxa2xx_mcxx_asst(speed, clock)
|
|
& MCXX_ASST_MASK) << MCXX_ASST_SHIFT)
|
|
| ((pxa2xx_mcxx_hold(speed, clock)
|
|
& MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxa2xx_pcmcia_set_mcio( int sock, int speed, int clock )
|
|
{
|
|
MCIO(sock) = ((pxa2xx_mcxx_setup(speed, clock)
|
|
& MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT)
|
|
| ((pxa2xx_mcxx_asst(speed, clock)
|
|
& MCXX_ASST_MASK) << MCXX_ASST_SHIFT)
|
|
| ((pxa2xx_mcxx_hold(speed, clock)
|
|
& MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxa2xx_pcmcia_set_mcatt( int sock, int speed, int clock )
|
|
{
|
|
MCATT(sock) = ((pxa2xx_mcxx_setup(speed, clock)
|
|
& MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT)
|
|
| ((pxa2xx_mcxx_asst(speed, clock)
|
|
& MCXX_ASST_MASK) << MCXX_ASST_SHIFT)
|
|
| ((pxa2xx_mcxx_hold(speed, clock)
|
|
& MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxa2xx_pcmcia_set_mcxx(struct soc_pcmcia_socket *skt, unsigned int clk)
|
|
{
|
|
struct soc_pcmcia_timing timing;
|
|
int sock = skt->nr;
|
|
|
|
soc_common_pcmcia_get_timing(skt, &timing);
|
|
|
|
pxa2xx_pcmcia_set_mcmem(sock, timing.mem, clk);
|
|
pxa2xx_pcmcia_set_mcatt(sock, timing.attr, clk);
|
|
pxa2xx_pcmcia_set_mcio(sock, timing.io, clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxa2xx_pcmcia_set_timing(struct soc_pcmcia_socket *skt)
|
|
{
|
|
unsigned int clk = get_memclk_frequency_10khz();
|
|
return pxa2xx_pcmcia_set_mcxx(skt, clk);
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_FREQ
|
|
|
|
static int
|
|
pxa2xx_pcmcia_frequency_change(struct soc_pcmcia_socket *skt,
|
|
unsigned long val,
|
|
struct cpufreq_freqs *freqs)
|
|
{
|
|
#warning "it's not clear if this is right since the core CPU (N) clock has no effect on the memory (L) clock"
|
|
switch (val) {
|
|
case CPUFREQ_PRECHANGE:
|
|
if (freqs->new > freqs->old) {
|
|
debug(skt, 2, "new frequency %u.%uMHz > %u.%uMHz, "
|
|
"pre-updating\n",
|
|
freqs->new / 1000, (freqs->new / 100) % 10,
|
|
freqs->old / 1000, (freqs->old / 100) % 10);
|
|
pxa2xx_pcmcia_set_mcxx(skt, freqs->new);
|
|
}
|
|
break;
|
|
|
|
case CPUFREQ_POSTCHANGE:
|
|
if (freqs->new < freqs->old) {
|
|
debug(skt, 2, "new frequency %u.%uMHz < %u.%uMHz, "
|
|
"post-updating\n",
|
|
freqs->new / 1000, (freqs->new / 100) % 10,
|
|
freqs->old / 1000, (freqs->old / 100) % 10);
|
|
pxa2xx_pcmcia_set_mcxx(skt, freqs->new);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int pxa2xx_drv_pcmcia_probe(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct pcmcia_low_level *ops;
|
|
int first, nr;
|
|
|
|
if (!dev || !dev->platform_data)
|
|
return -ENODEV;
|
|
|
|
ops = (struct pcmcia_low_level *)dev->platform_data;
|
|
first = ops->first;
|
|
nr = ops->nr;
|
|
|
|
/* Provide our PXA2xx specific timing routines. */
|
|
ops->set_timing = pxa2xx_pcmcia_set_timing;
|
|
#ifdef CONFIG_CPU_FREQ
|
|
ops->frequency_change = pxa2xx_pcmcia_frequency_change;
|
|
#endif
|
|
|
|
ret = soc_common_drv_pcmcia_probe(dev, ops, first, nr);
|
|
|
|
if (ret == 0) {
|
|
/*
|
|
* We have at least one socket, so set MECR:CIT
|
|
* (Card Is There)
|
|
*/
|
|
MECR |= MECR_CIT;
|
|
|
|
/* Set MECR:NOS (Number Of Sockets) */
|
|
if (nr > 1)
|
|
MECR |= MECR_NOS;
|
|
else
|
|
MECR &= ~MECR_NOS;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(pxa2xx_drv_pcmcia_probe);
|
|
|
|
static int pxa2xx_drv_pcmcia_resume(struct device *dev)
|
|
{
|
|
struct pcmcia_low_level *ops = dev->platform_data;
|
|
int nr = ops ? ops->nr : 0;
|
|
|
|
MECR = nr > 1 ? MECR_CIT | MECR_NOS : (nr > 0 ? MECR_CIT : 0);
|
|
|
|
return pcmcia_socket_dev_resume(dev);
|
|
}
|
|
|
|
static struct device_driver pxa2xx_pcmcia_driver = {
|
|
.probe = pxa2xx_drv_pcmcia_probe,
|
|
.remove = soc_common_drv_pcmcia_remove,
|
|
.suspend = pcmcia_socket_dev_suspend,
|
|
.resume = pxa2xx_drv_pcmcia_resume,
|
|
.name = "pxa2xx-pcmcia",
|
|
.bus = &platform_bus_type,
|
|
};
|
|
|
|
static int __init pxa2xx_pcmcia_init(void)
|
|
{
|
|
return driver_register(&pxa2xx_pcmcia_driver);
|
|
}
|
|
|
|
static void __exit pxa2xx_pcmcia_exit(void)
|
|
{
|
|
driver_unregister(&pxa2xx_pcmcia_driver);
|
|
}
|
|
|
|
fs_initcall(pxa2xx_pcmcia_init);
|
|
module_exit(pxa2xx_pcmcia_exit);
|
|
|
|
MODULE_AUTHOR("Stefan Eletzhofer <stefan.eletzhofer@inquant.de> and Ian Molton <spyro@f2s.com>");
|
|
MODULE_DESCRIPTION("Linux PCMCIA Card Services: PXA2xx core socket driver");
|
|
MODULE_LICENSE("GPL");
|