📄 hd64465_ss.c
字号:
/* * $Id$ * * Device driver for the PCMCIA controller module of the * Hitachi HD64465 handheld companion chip. * * Note that the HD64465 provides a very thin PCMCIA host bridge * layer, requiring a lot of the work of supporting cards to be * performed by the processor. For example: mapping of card * interrupts to processor IRQs is done by IRQ demuxing software; * IO and memory mappings are fixed; setting voltages according * to card Voltage Select pins etc is done in software. * * Note also that this driver uses only the simple, fixed, * 16MB, 16-bit wide mappings to PCMCIA spaces defined by the * HD64465. Larger mappings, smaller mappings, or mappings of * different width to the same socket, are all possible only by * involving the SH7750's MMU, which is considered unnecessary here. * The downside is that it may be possible for some drivers to * break because they need or expect 8-bit mappings. * * This driver currently supports only the following configuration: * SH7750 CPU, HD64465, TPS2206 voltage control chip. * * by Greg Banks <gbanks@pocketpenguins.com> * (c) 2000 PocketPenguins Inc * */#include <linux/types.h>#include <linux/module.h>#include <linux/init.h>#include <linux/string.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/mm.h>#include <linux/vmalloc.h>#include <asm/errno.h>#include <linux/irq.h>#include <asm/io.h>#include <asm/hd64465.h>#include <pcmcia/version.h>#include <pcmcia/cs_types.h>#include <pcmcia/cs.h>#include <pcmcia/ss.h>#include <pcmcia/bulkmem.h>#include <pcmcia/cistpl.h>#include "cs_internal.h"#define MODNAME "hd64465_ss"/* #define HD64465_DEBUG 1 */#ifndef HD64465_DEBUG#define HD64465_DEBUG 0#endif#if HD64465_DEBUG#define DPRINTK(args...) printk(MODNAME ": " args)#else#define DPRINTK(args...)#endifextern int hd64465_io_debug;/*============================================================*/#define HS_IO_MAP_SIZE (64*1024)typedef struct hs_socket_t{ u_int irq; u_long mem_base; u_long mem_length; void (*handler)(void *info, u_int events); void *handler_info; u_int pending_events; u_int ctrl_base; socket_state_t state; pccard_io_map io_maps[MAX_IO_WIN]; pccard_mem_map mem_maps[MAX_WIN]; struct vm_struct *io_vma; /* allocated kernel vm for mapping IO space */} hs_socket_t;#define HS_MAX_SOCKETS 2static hs_socket_t hs_sockets[HS_MAX_SOCKETS];static spinlock_t hs_pending_event_lock = SPIN_LOCK_UNLOCKED;/* Calculate socket number from ptr into hs_sockets[] */#define hs_sockno(sp) (sp - hs_sockets)static socket_cap_t hs_socket_cap ={ SS_CAP_PCCARD /* support 16 bit cards */ |SS_CAP_STATIC_MAP /* mappings are fixed in host memory */ , 0xffde/*0xffff*/, /* IRQs mapped in s/w so can do any, really */ HD64465_PCC_WINDOW, /* 16MB fixed window size */ 0, /* no PCI support */ 0, /* no CardBus support */ 0 /* no bus operations needed */};#define hs_in(sp, r) inb((sp)->ctrl_base + (r))#define hs_out(sp, v, r) outb(v, (sp)->ctrl_base + (r))/* translate a boolean value to a bit in a register */#define bool_to_regbit(sp, r, bi, bo) \ do { \ unsigned short v = hs_in(sp, r); \ if (bo) \ v |= (bi); \ else \ v &= ~(bi); \ hs_out(sp, v, r); \ } while(0) /* register offsets from HD64465_REG_PCC[01]ISR */#define ISR 0x0#define GCR 0x2#define CSCR 0x4#define CSCIER 0x6#define SCR 0x8/* Mask and values for CSCIER register */#define IER_MASK 0x80#define IER_ON 0x3f /* interrupts on */#define IER_OFF 0x00 /* interrupts off *//*============================================================*/#if HD64465_DEBUG > 10static void cis_hex_dump(const unsigned char *x, int len){ int i; for (i=0 ; i<len ; i++) { if (!(i & 0xf)) printk("\n%08x", (unsigned)(x + i)); printk(" %02x", *(volatile unsigned short*)x); x += 2; } printk("\n");}#endif/*============================================================*//* * This code helps create the illusion that the IREQ line from * the PC card is mapped to one of the CPU's IRQ lines by the * host bridge hardware (which is how every host bridge *except* * the HD64465 works). In particular, it supports enabling * and disabling the IREQ line by code which knows nothing * about the host bridge (e.g. device drivers, IDE code) using * the request_irq(), free_irq(), probe_irq_on() and probe_irq_off() * functions. Also, it supports sharing the mapped IRQ with * real hardware IRQs from the -IRL0-3 lines. */#define HS_NUM_MAPPED_IRQS 16 /* Limitation of the PCMCIA code */static struct{ /* index is mapped irq number */ hs_socket_t *sock; hw_irq_controller *old_handler;} hs_mapped_irq[HS_NUM_MAPPED_IRQS];static void hs_socket_enable_ireq(hs_socket_t *sp){ unsigned short cscier; DPRINTK("hs_socket_enable_ireq(sock=%d)\n", hs_sockno(sp)); cscier = hs_in(sp, CSCIER); cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; cscier |= HD64465_PCCCSCIER_PIREQE_LEVEL; hs_out(sp, cscier, CSCIER);}static void hs_socket_disable_ireq(hs_socket_t *sp){ unsigned short cscier; DPRINTK("hs_socket_disable_ireq(sock=%d)\n", hs_sockno(sp)); cscier = hs_in(sp, CSCIER); cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; hs_out(sp, cscier, CSCIER);}static unsigned int hs_startup_irq(unsigned int irq){ hs_socket_enable_ireq(hs_mapped_irq[irq].sock); hs_mapped_irq[irq].old_handler->startup(irq); return 0;}static void hs_shutdown_irq(unsigned int irq){ hs_socket_disable_ireq(hs_mapped_irq[irq].sock); hs_mapped_irq[irq].old_handler->shutdown(irq);}static void hs_enable_irq(unsigned int irq){ hs_socket_enable_ireq(hs_mapped_irq[irq].sock); hs_mapped_irq[irq].old_handler->enable(irq);}static void hs_disable_irq(unsigned int irq){ hs_socket_disable_ireq(hs_mapped_irq[irq].sock); hs_mapped_irq[irq].old_handler->disable(irq);}extern struct hw_interrupt_type no_irq_type;static void hs_mask_and_ack_irq(unsigned int irq){ hs_socket_disable_ireq(hs_mapped_irq[irq].sock); /* ack_none() spuriously complains about an unexpected IRQ */ if (hs_mapped_irq[irq].old_handler != &no_irq_type) hs_mapped_irq[irq].old_handler->ack(irq);}static void hs_end_irq(unsigned int irq){ hs_socket_enable_ireq(hs_mapped_irq[irq].sock); hs_mapped_irq[irq].old_handler->end(irq);}static struct hw_interrupt_type hd64465_ss_irq_type = { typename: "PCMCIA-IRQ", startup: hs_startup_irq, shutdown: hs_shutdown_irq, enable: hs_enable_irq, disable: hs_disable_irq, ack: hs_mask_and_ack_irq, end: hs_end_irq};/* * This function should only ever be called with interrupts disabled. */static void hs_map_irq(hs_socket_t *sp, unsigned int irq){ DPRINTK("hs_map_irq(sock=%d irq=%d)\n", hs_sockno(sp), irq); if (irq >= HS_NUM_MAPPED_IRQS) return; hs_mapped_irq[irq].sock = sp; /* insert ourselves as the irq controller */ hs_mapped_irq[irq].old_handler = irq_desc[irq].handler; irq_desc[irq].handler = &hd64465_ss_irq_type;}/* * This function should only ever be called with interrupts disabled. */static void hs_unmap_irq(hs_socket_t *sp, unsigned int irq){ DPRINTK("hs_unmap_irq(sock=%d irq=%d)\n", hs_sockno(sp), irq); if (irq >= HS_NUM_MAPPED_IRQS) return; /* restore the original irq controller */ irq_desc[irq].handler = hs_mapped_irq[irq].old_handler;}/*============================================================*//* * Set Vpp and Vcc (in tenths of a Volt). Does not * support the hi-Z state. * * Note, this assumes the board uses a TPS2206 chip to control * the Vcc and Vpp voltages to the hs_sockets. If your board * uses the MIC2563 (also supported by the HD64465) then you * will have to modify this function. */ /* 0V 3.3V 5.5V */static const u_char hs_tps2206_avcc[3] = { 0x00, 0x04, 0x08 };static const u_char hs_tps2206_bvcc[3] = { 0x00, 0x80, 0x40 };static int hs_set_voltages(hs_socket_t *sp, int Vcc, int Vpp){ u_int psr; u_int vcci = 0; u_int sock = hs_sockno(sp); DPRINTK("hs_set_voltage(%d, %d, %d)\n", sock, Vcc, Vpp); switch (Vcc) { case 0: vcci = 0; break; case 33: vcci = 1; break; case 50: vcci = 2; break; default: return 0; } /* Note: Vpp = 120 not supported -- Greg Banks */ if (Vpp != 0 && Vpp != Vcc) return 0; /* The PSR register holds 8 of the 9 bits which control * the TPS2206 via its serial interface. */ psr = inw(HD64465_REG_PCCPSR); switch (sock) { case 0: psr &= 0x0f; psr |= hs_tps2206_avcc[vcci]; psr |= (Vpp == 0 ? 0x00 : 0x02); break; case 1: psr &= 0xf0; psr |= hs_tps2206_bvcc[vcci]; psr |= (Vpp == 0 ? 0x00 : 0x20); break; }; outw(psr, HD64465_REG_PCCPSR); return 1;}/*============================================================*//* * Drive the RESET line to the card. */static void hs_reset_socket(hs_socket_t *sp, int on){ unsigned short v; v = hs_in(sp, GCR); if (on) v |= HD64465_PCCGCR_PCCR; else v &= ~HD64465_PCCGCR_PCCR; hs_out(sp, v, GCR);}/*============================================================*/static int hs_init(unsigned int sock){ hs_socket_t *sp = &hs_sockets[sock]; DPRINTK("hs_init(%d)\n", sock); sp->pending_events = 0; sp->state.Vcc = 0; sp->state.Vpp = 0; hs_set_voltages(sp, 0, 0); return 0;}/*============================================================*/static int hs_suspend(unsigned int sock){ DPRINTK("hs_suspend(%d)\n", sock); /* TODO */ return 0;}/*============================================================*/static int hs_register_callback(unsigned int sock, void (*handler)(void *, unsigned int), void * info){ hs_socket_t *sp = &hs_sockets[sock]; DPRINTK("hs_register_callback(%d)\n", sock); sp->handler = handler; sp->handler_info = info; if (handler == 0) { MOD_DEC_USE_COUNT; } else { MOD_INC_USE_COUNT; } return 0;}/*============================================================*/static int hs_inquire_socket(unsigned int sock, socket_cap_t *cap){ DPRINTK("hs_inquire_socket(%d)\n", sock); *cap = hs_socket_cap; return 0;}/*============================================================*/static int hs_get_status(unsigned int sock, u_int *value){ hs_socket_t *sp = &hs_sockets[sock]; unsigned int isr; u_int status = 0; isr = hs_in(sp, ISR); /* Card is seated and powered when *both* CD pins are low */ if ((isr & HD64465_PCCISR_PCD_MASK) == 0) { status |= SS_DETECT; /* card present */ switch (isr & HD64465_PCCISR_PBVD_MASK) { case HD64465_PCCISR_PBVD_BATGOOD: break; case HD64465_PCCISR_PBVD_BATWARN: status |= SS_BATWARN; break; default: status |= SS_BATDEAD; break; } if (isr & HD64465_PCCISR_PREADY) status |= SS_READY; if (isr & HD64465_PCCISR_PMWP) status |= SS_WRPROT; /* Voltage Select pins interpreted as per Table 4-5 of the std. * Assuming we have the TPS2206, the socket is a "Low Voltage * key, 3.3V and 5V available, no X.XV available". */ switch (isr & (HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1)) { case HD64465_PCCISR_PVS1: printk(KERN_NOTICE MODNAME ": cannot handle X.XV card, ignored\n"); status = 0; break; case 0: case HD64465_PCCISR_PVS2: /* 3.3V */ status |= SS_3VCARD; break; case HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1: /* 5V */ break; } /* TODO: SS_POWERON */ /* TODO: SS_STSCHG */ } DPRINTK("hs_get_status(%d) = %x\n", sock, status); *value = status; return 0;}/*============================================================*/static int hs_get_socket(unsigned int sock, socket_state_t *state){ hs_socket_t *sp = &hs_sockets[sock]; DPRINTK("hs_get_socket(%d)\n", sock); *state = sp->state; return 0;}/*============================================================*/static int hs_set_socket(unsigned int sock, socket_state_t *state){ hs_socket_t *sp = &hs_sockets[sock]; u_long flags; u_int changed; unsigned short cscier; DPRINTK("hs_set_socket(sock=%d, flags=%x, csc_mask=%x, Vcc=%d, Vpp=%d, io_irq=%d)\n", sock, state->flags, state->csc_mask, state->Vcc, state->Vpp, state->io_irq); save_and_cli(flags); /* Don't want interrupts happening here */ if (state->Vpp != sp->state.Vpp || state->Vcc != sp->state.Vcc) { if (!hs_set_voltages(sp, state->Vcc, state->Vpp)) { restore_flags(flags); return -EINVAL; } }/* hd64465_io_debug = 1; */ /* * Handle changes in the Card Status Change mask, * by propagating to the CSCR register */ changed = sp->state.csc_mask ^ state->csc_mask; cscier = hs_in(sp, CSCIER); if (changed & SS_DETECT) { if (state->csc_mask & SS_DETECT) cscier |= HD64465_PCCCSCIER_PCDE; else cscier &= ~HD64465_PCCCSCIER_PCDE; } if (changed & SS_READY) { if (state->csc_mask & SS_READY) cscier |= HD64465_PCCCSCIER_PRE; else cscier &= ~HD64465_PCCCSCIER_PRE; } if (changed & SS_BATDEAD) { if (state->csc_mask & SS_BATDEAD) cscier |= HD64465_PCCCSCIER_PBDE; else cscier &= ~HD64465_PCCCSCIER_PBDE; } if (changed & SS_BATWARN) { if (state->csc_mask & SS_BATWARN) cscier |= HD64465_PCCCSCIER_PBWE; else cscier &= ~HD64465_PCCCSCIER_PBWE;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -