mirror of
https://github.com/torvalds/linux.git
synced 2024-12-22 10:56:40 +00:00
24d6572b4f
Recently I've been trying to get working PCMCIA interface on H5000 ipaq series, using dual PCMCIA sleeve. So far things work correctly, but I had to do one modification to drivers/pcmcia/pxa2xx_base.c to get the interface working with orinoco gold PCMCIA card (wired pcnet_cs ethernet card worked even without this modification). The issue has something to do with assert time on PCMCIA bus, but I'm not really sure what -- I found the working value just by trial&error approach. I'm not sure how is the assert value in pxa2xx_mcxx_asst calculated (I know, simple formula, but the reason why is it calculated that way is not obvious for me), neither that my modification is correct. It just works with iPAQ. Cc: Russell King <rmk@arm.linux.org.uk> Cc: Richard Purdie <rpurdie@rpsys.net> Cc: Dominik Brodowski <linux@dominikbrodowski.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
260 lines
6.6 KiB
C
260 lines
6.6 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/cpufreq.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/platform_device.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_probe(struct platform_device *dev)
|
|
{
|
|
return __pxa2xx_drv_pcmcia_probe(&dev->dev);
|
|
}
|
|
|
|
static int pxa2xx_drv_pcmcia_remove(struct platform_device *dev)
|
|
{
|
|
return soc_common_drv_pcmcia_remove(&dev->dev);
|
|
}
|
|
|
|
static int pxa2xx_drv_pcmcia_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
return pcmcia_socket_dev_suspend(&dev->dev, state);
|
|
}
|
|
|
|
static int pxa2xx_drv_pcmcia_resume(struct platform_device *dev)
|
|
{
|
|
struct pcmcia_low_level *ops = dev->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->dev);
|
|
}
|
|
|
|
static struct platform_driver pxa2xx_pcmcia_driver = {
|
|
.probe = pxa2xx_drv_pcmcia_probe,
|
|
.remove = pxa2xx_drv_pcmcia_remove,
|
|
.suspend = pxa2xx_drv_pcmcia_suspend,
|
|
.resume = pxa2xx_drv_pcmcia_resume,
|
|
.driver = {
|
|
.name = "pxa2xx-pcmcia",
|
|
},
|
|
};
|
|
|
|
static int __init pxa2xx_pcmcia_init(void)
|
|
{
|
|
return platform_driver_register(&pxa2xx_pcmcia_driver);
|
|
}
|
|
|
|
static void __exit pxa2xx_pcmcia_exit(void)
|
|
{
|
|
platform_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");
|