264 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			264 lines
		
	
	
		
			7.3 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 "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;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
#endif
 | 
						|
 | 
						|
static int
 | 
						|
sa1100_pcmcia_set_timing(struct soc_pcmcia_socket *skt)
 | 
						|
{
 | 
						|
	unsigned long clk = clk_get_rate(skt->clk);
 | 
						|
 | 
						|
	return sa1100_pcmcia_set_mecr(skt, clk / 1000);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
sa1100_pcmcia_show_timing(struct soc_pcmcia_socket *skt, char *buf)
 | 
						|
{
 | 
						|
	struct soc_pcmcia_timing timing;
 | 
						|
	unsigned int clock = clk_get_rate(skt->clk) / 1000;
 | 
						|
	unsigned long mecr = MECR;
 | 
						|
	char *p = buf;
 | 
						|
 | 
						|
	soc_common_pcmcia_get_timing(skt, &timing);
 | 
						|
 | 
						|
	p+=sprintf(p, "I/O      : %uns (%uns)\n", timing.io,
 | 
						|
		   sa1100_pcmcia_cmd_time(clock, MECR_BSIO_GET(mecr, skt->nr)));
 | 
						|
 | 
						|
	p+=sprintf(p, "attribute: %uns (%uns)\n", timing.attr,
 | 
						|
		   sa1100_pcmcia_cmd_time(clock, MECR_BSA_GET(mecr, skt->nr)));
 | 
						|
 | 
						|
	p+=sprintf(p, "common   : %uns (%uns)\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;
 | 
						|
	struct clk *clk;
 | 
						|
 | 
						|
	clk = devm_clk_get(dev, NULL);
 | 
						|
	if (IS_ERR(clk))
 | 
						|
		return PTR_ERR(clk);
 | 
						|
 | 
						|
	sa11xx_drv_pcmcia_ops(ops);
 | 
						|
 | 
						|
	sinfo = devm_kzalloc(dev, SKT_DEV_INFO_SIZE(nr), GFP_KERNEL);
 | 
						|
	if (!sinfo)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	sinfo->nskt = nr;
 | 
						|
 | 
						|
	/* Initialize processor specific parameters */
 | 
						|
	for (i = 0; i < nr; i++) {
 | 
						|
		skt = &sinfo->skt[i];
 | 
						|
 | 
						|
		skt->nr = first + i;
 | 
						|
		skt->clk = clk;
 | 
						|
		soc_pcmcia_init_one(skt, ops, dev);
 | 
						|
 | 
						|
		ret = sa11xx_drv_pcmcia_add_one(skt);
 | 
						|
		if (ret)
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ret) {
 | 
						|
		while (--i >= 0)
 | 
						|
			soc_pcmcia_remove_one(&sinfo->skt[i]);
 | 
						|
	} else {
 | 
						|
		dev_set_drvdata(dev, sinfo);
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL(sa11xx_drv_pcmcia_probe);
 | 
						|
 | 
						|
MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>");
 | 
						|
MODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11xx core socket driver");
 | 
						|
MODULE_LICENSE("Dual MPL/GPL");
 |