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");
 | 
