Commit 553d6d5f5b84f11fad8043688137dac96df1a06d
Committed by
Ralf Baechle
1 parent
971842677c
Exists in
master
and in
39 other branches
MIPS: BCM63xx: Add PCMCIA & Cardbus support.
Signed-off-by: Maxime Bizon <mbizon@freebox.fr> Reviewed-by: Wolfram Sang <w.sang@pengutronix.de> Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Showing 8 changed files with 763 additions and 1 deletions Side-by-side Diff
arch/mips/bcm63xx/Makefile
arch/mips/bcm63xx/boards/board_bcm963xx.c
... | ... | @@ -23,6 +23,7 @@ |
23 | 23 | #include <bcm63xx_dev_pci.h> |
24 | 24 | #include <bcm63xx_dev_enet.h> |
25 | 25 | #include <bcm63xx_dev_dsp.h> |
26 | +#include <bcm63xx_dev_pcmcia.h> | |
26 | 27 | #include <bcm63xx_dev_uart.h> |
27 | 28 | #include <board_bcm963xx.h> |
28 | 29 | |
... | ... | @@ -794,6 +795,9 @@ |
794 | 795 | u32 val; |
795 | 796 | |
796 | 797 | bcm63xx_uart_register(); |
798 | + | |
799 | + if (board.has_pccard) | |
800 | + bcm63xx_pcmcia_register(); | |
797 | 801 | |
798 | 802 | if (board.has_enet0 && |
799 | 803 | !board_get_mac_address(board.enet0.mac_addr)) |
arch/mips/bcm63xx/dev-pcmcia.c
1 | +/* | |
2 | + * This file is subject to the terms and conditions of the GNU General Public | |
3 | + * License. See the file "COPYING" in the main directory of this archive | |
4 | + * for more details. | |
5 | + * | |
6 | + * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr> | |
7 | + */ | |
8 | + | |
9 | +#include <linux/init.h> | |
10 | +#include <linux/kernel.h> | |
11 | +#include <asm/bootinfo.h> | |
12 | +#include <linux/platform_device.h> | |
13 | +#include <bcm63xx_cs.h> | |
14 | +#include <bcm63xx_cpu.h> | |
15 | +#include <bcm63xx_dev_pcmcia.h> | |
16 | +#include <bcm63xx_io.h> | |
17 | +#include <bcm63xx_regs.h> | |
18 | + | |
19 | +static struct resource pcmcia_resources[] = { | |
20 | + /* pcmcia registers */ | |
21 | + { | |
22 | + /* start & end filled at runtime */ | |
23 | + .flags = IORESOURCE_MEM, | |
24 | + }, | |
25 | + | |
26 | + /* pcmcia memory zone resources */ | |
27 | + { | |
28 | + .start = BCM_PCMCIA_COMMON_BASE_PA, | |
29 | + .end = BCM_PCMCIA_COMMON_END_PA, | |
30 | + .flags = IORESOURCE_MEM, | |
31 | + }, | |
32 | + { | |
33 | + .start = BCM_PCMCIA_ATTR_BASE_PA, | |
34 | + .end = BCM_PCMCIA_ATTR_END_PA, | |
35 | + .flags = IORESOURCE_MEM, | |
36 | + }, | |
37 | + { | |
38 | + .start = BCM_PCMCIA_IO_BASE_PA, | |
39 | + .end = BCM_PCMCIA_IO_END_PA, | |
40 | + .flags = IORESOURCE_MEM, | |
41 | + }, | |
42 | + | |
43 | + /* PCMCIA irq */ | |
44 | + { | |
45 | + /* start filled at runtime */ | |
46 | + .flags = IORESOURCE_IRQ, | |
47 | + }, | |
48 | + | |
49 | + /* declare PCMCIA IO resource also */ | |
50 | + { | |
51 | + .start = BCM_PCMCIA_IO_BASE_PA, | |
52 | + .end = BCM_PCMCIA_IO_END_PA, | |
53 | + .flags = IORESOURCE_IO, | |
54 | + }, | |
55 | +}; | |
56 | + | |
57 | +static struct bcm63xx_pcmcia_platform_data pd; | |
58 | + | |
59 | +static struct platform_device bcm63xx_pcmcia_device = { | |
60 | + .name = "bcm63xx_pcmcia", | |
61 | + .id = 0, | |
62 | + .num_resources = ARRAY_SIZE(pcmcia_resources), | |
63 | + .resource = pcmcia_resources, | |
64 | + .dev = { | |
65 | + .platform_data = &pd, | |
66 | + }, | |
67 | +}; | |
68 | + | |
69 | +static int __init config_pcmcia_cs(unsigned int cs, | |
70 | + u32 base, unsigned int size) | |
71 | +{ | |
72 | + int ret; | |
73 | + | |
74 | + ret = bcm63xx_set_cs_status(cs, 0); | |
75 | + if (!ret) | |
76 | + ret = bcm63xx_set_cs_base(cs, base, size); | |
77 | + if (!ret) | |
78 | + ret = bcm63xx_set_cs_status(cs, 1); | |
79 | + return ret; | |
80 | +} | |
81 | + | |
82 | +static const __initdata struct { | |
83 | + unsigned int cs; | |
84 | + unsigned int base; | |
85 | + unsigned int size; | |
86 | +} pcmcia_cs[3] = { | |
87 | + { | |
88 | + .cs = MPI_CS_PCMCIA_COMMON, | |
89 | + .base = BCM_PCMCIA_COMMON_BASE_PA, | |
90 | + .size = BCM_PCMCIA_COMMON_SIZE | |
91 | + }, | |
92 | + { | |
93 | + .cs = MPI_CS_PCMCIA_ATTR, | |
94 | + .base = BCM_PCMCIA_ATTR_BASE_PA, | |
95 | + .size = BCM_PCMCIA_ATTR_SIZE | |
96 | + }, | |
97 | + { | |
98 | + .cs = MPI_CS_PCMCIA_IO, | |
99 | + .base = BCM_PCMCIA_IO_BASE_PA, | |
100 | + .size = BCM_PCMCIA_IO_SIZE | |
101 | + }, | |
102 | +}; | |
103 | + | |
104 | +int __init bcm63xx_pcmcia_register(void) | |
105 | +{ | |
106 | + int ret, i; | |
107 | + | |
108 | + if (!BCMCPU_IS_6348() && !BCMCPU_IS_6358()) | |
109 | + return 0; | |
110 | + | |
111 | + /* use correct pcmcia ready gpio depending on processor */ | |
112 | + switch (bcm63xx_get_cpu_id()) { | |
113 | + case BCM6348_CPU_ID: | |
114 | + pd.ready_gpio = 22; | |
115 | + break; | |
116 | + | |
117 | + case BCM6358_CPU_ID: | |
118 | + pd.ready_gpio = 18; | |
119 | + break; | |
120 | + | |
121 | + default: | |
122 | + return -ENODEV; | |
123 | + } | |
124 | + | |
125 | + pcmcia_resources[0].start = bcm63xx_regset_address(RSET_PCMCIA); | |
126 | + pcmcia_resources[0].end = pcmcia_resources[0].start + | |
127 | + RSET_PCMCIA_SIZE - 1; | |
128 | + pcmcia_resources[4].start = bcm63xx_get_irq_number(IRQ_PCMCIA); | |
129 | + | |
130 | + /* configure pcmcia chip selects */ | |
131 | + for (i = 0; i < 3; i++) { | |
132 | + ret = config_pcmcia_cs(pcmcia_cs[i].cs, | |
133 | + pcmcia_cs[i].base, | |
134 | + pcmcia_cs[i].size); | |
135 | + if (ret) | |
136 | + goto out_err; | |
137 | + } | |
138 | + | |
139 | + return platform_device_register(&bcm63xx_pcmcia_device); | |
140 | + | |
141 | +out_err: | |
142 | + printk(KERN_ERR "unable to set pcmcia chip select\n"); | |
143 | + return ret; | |
144 | +} |
arch/mips/include/asm/mach-bcm63xx/bcm63xx_dev_pcmcia.h
drivers/pcmcia/Kconfig
... | ... | @@ -192,6 +192,10 @@ |
192 | 192 | tristate "Au1x00 pcmcia support" |
193 | 193 | depends on SOC_AU1X00 && PCMCIA |
194 | 194 | |
195 | +config PCMCIA_BCM63XX | |
196 | + tristate "bcm63xx pcmcia support" | |
197 | + depends on BCM63XX && PCMCIA | |
198 | + | |
195 | 199 | config PCMCIA_SA1100 |
196 | 200 | tristate "SA1100 support" |
197 | 201 | depends on ARM && ARCH_SA1100 && PCMCIA |
drivers/pcmcia/Makefile
... | ... | @@ -27,6 +27,7 @@ |
27 | 27 | obj-$(CONFIG_M32R_PCC) += m32r_pcc.o |
28 | 28 | obj-$(CONFIG_M32R_CFC) += m32r_cfc.o |
29 | 29 | obj-$(CONFIG_PCMCIA_AU1X00) += au1x00_ss.o |
30 | +obj-$(CONFIG_PCMCIA_BCM63XX) += bcm63xx_pcmcia.o | |
30 | 31 | obj-$(CONFIG_PCMCIA_VRC4171) += vrc4171_card.o |
31 | 32 | obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o |
32 | 33 | obj-$(CONFIG_OMAP_CF) += omap_cf.o |
drivers/pcmcia/bcm63xx_pcmcia.c
1 | +/* | |
2 | + * This file is subject to the terms and conditions of the GNU General Public | |
3 | + * License. See the file "COPYING" in the main directory of this archive | |
4 | + * for more details. | |
5 | + * | |
6 | + * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr> | |
7 | + */ | |
8 | + | |
9 | +#include <linux/kernel.h> | |
10 | +#include <linux/module.h> | |
11 | +#include <linux/ioport.h> | |
12 | +#include <linux/timer.h> | |
13 | +#include <linux/platform_device.h> | |
14 | +#include <linux/delay.h> | |
15 | +#include <linux/pci.h> | |
16 | +#include <linux/gpio.h> | |
17 | + | |
18 | +#include <bcm63xx_regs.h> | |
19 | +#include <bcm63xx_io.h> | |
20 | +#include "bcm63xx_pcmcia.h" | |
21 | + | |
22 | +#define PFX "bcm63xx_pcmcia: " | |
23 | + | |
24 | +#ifdef CONFIG_CARDBUS | |
25 | +/* if cardbus is used, platform device needs reference to actual pci | |
26 | + * device */ | |
27 | +static struct pci_dev *bcm63xx_cb_dev; | |
28 | +#endif | |
29 | + | |
30 | +/* | |
31 | + * read/write helper for pcmcia regs | |
32 | + */ | |
33 | +static inline u32 pcmcia_readl(struct bcm63xx_pcmcia_socket *skt, u32 off) | |
34 | +{ | |
35 | + return bcm_readl(skt->base + off); | |
36 | +} | |
37 | + | |
38 | +static inline void pcmcia_writel(struct bcm63xx_pcmcia_socket *skt, | |
39 | + u32 val, u32 off) | |
40 | +{ | |
41 | + bcm_writel(val, skt->base + off); | |
42 | +} | |
43 | + | |
44 | +/* | |
45 | + * This callback should (re-)initialise the socket, turn on status | |
46 | + * interrupts and PCMCIA bus, and wait for power to stabilise so that | |
47 | + * the card status signals report correctly. | |
48 | + * | |
49 | + * Hardware cannot do that. | |
50 | + */ | |
51 | +static int bcm63xx_pcmcia_sock_init(struct pcmcia_socket *sock) | |
52 | +{ | |
53 | + return 0; | |
54 | +} | |
55 | + | |
56 | +/* | |
57 | + * This callback should remove power on the socket, disable IRQs from | |
58 | + * the card, turn off status interrupts, and disable the PCMCIA bus. | |
59 | + * | |
60 | + * Hardware cannot do that. | |
61 | + */ | |
62 | +static int bcm63xx_pcmcia_suspend(struct pcmcia_socket *sock) | |
63 | +{ | |
64 | + return 0; | |
65 | +} | |
66 | + | |
67 | +/* | |
68 | + * Implements the set_socket() operation for the in-kernel PCMCIA | |
69 | + * service (formerly SS_SetSocket in Card Services). We more or | |
70 | + * less punt all of this work and let the kernel handle the details | |
71 | + * of power configuration, reset, &c. We also record the value of | |
72 | + * `state' in order to regurgitate it to the PCMCIA core later. | |
73 | + */ | |
74 | +static int bcm63xx_pcmcia_set_socket(struct pcmcia_socket *sock, | |
75 | + socket_state_t *state) | |
76 | +{ | |
77 | + struct bcm63xx_pcmcia_socket *skt; | |
78 | + unsigned long flags; | |
79 | + u32 val; | |
80 | + | |
81 | + skt = sock->driver_data; | |
82 | + | |
83 | + spin_lock_irqsave(&skt->lock, flags); | |
84 | + | |
85 | + /* note: hardware cannot control socket power, so we will | |
86 | + * always report SS_POWERON */ | |
87 | + | |
88 | + /* apply socket reset */ | |
89 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
90 | + if (state->flags & SS_RESET) | |
91 | + val |= PCMCIA_C1_RESET_MASK; | |
92 | + else | |
93 | + val &= ~PCMCIA_C1_RESET_MASK; | |
94 | + | |
95 | + /* reverse reset logic for cardbus card */ | |
96 | + if (skt->card_detected && (skt->card_type & CARD_CARDBUS)) | |
97 | + val ^= PCMCIA_C1_RESET_MASK; | |
98 | + | |
99 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
100 | + | |
101 | + /* keep requested state for event reporting */ | |
102 | + skt->requested_state = *state; | |
103 | + | |
104 | + spin_unlock_irqrestore(&skt->lock, flags); | |
105 | + | |
106 | + return 0; | |
107 | +} | |
108 | + | |
109 | +/* | |
110 | + * identity cardtype from VS[12] input, CD[12] input while only VS2 is | |
111 | + * floating, and CD[12] input while only VS1 is floating | |
112 | + */ | |
113 | +enum { | |
114 | + IN_VS1 = (1 << 0), | |
115 | + IN_VS2 = (1 << 1), | |
116 | + IN_CD1_VS2H = (1 << 2), | |
117 | + IN_CD2_VS2H = (1 << 3), | |
118 | + IN_CD1_VS1H = (1 << 4), | |
119 | + IN_CD2_VS1H = (1 << 5), | |
120 | +}; | |
121 | + | |
122 | +static const u8 vscd_to_cardtype[] = { | |
123 | + | |
124 | + /* VS1 float, VS2 float */ | |
125 | + [IN_VS1 | IN_VS2] = (CARD_PCCARD | CARD_5V), | |
126 | + | |
127 | + /* VS1 grounded, VS2 float */ | |
128 | + [IN_VS2] = (CARD_PCCARD | CARD_5V | CARD_3V), | |
129 | + | |
130 | + /* VS1 grounded, VS2 grounded */ | |
131 | + [0] = (CARD_PCCARD | CARD_5V | CARD_3V | CARD_XV), | |
132 | + | |
133 | + /* VS1 tied to CD1, VS2 float */ | |
134 | + [IN_VS1 | IN_VS2 | IN_CD1_VS1H] = (CARD_CARDBUS | CARD_3V), | |
135 | + | |
136 | + /* VS1 grounded, VS2 tied to CD2 */ | |
137 | + [IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V | CARD_XV), | |
138 | + | |
139 | + /* VS1 tied to CD2, VS2 grounded */ | |
140 | + [IN_VS1 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_3V | CARD_XV | CARD_YV), | |
141 | + | |
142 | + /* VS1 float, VS2 grounded */ | |
143 | + [IN_VS1] = (CARD_PCCARD | CARD_XV), | |
144 | + | |
145 | + /* VS1 float, VS2 tied to CD2 */ | |
146 | + [IN_VS1 | IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V), | |
147 | + | |
148 | + /* VS1 float, VS2 tied to CD1 */ | |
149 | + [IN_VS1 | IN_VS2 | IN_CD1_VS2H] = (CARD_CARDBUS | CARD_XV | CARD_YV), | |
150 | + | |
151 | + /* VS1 tied to CD2, VS2 float */ | |
152 | + [IN_VS1 | IN_VS2 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_YV), | |
153 | + | |
154 | + /* VS2 grounded, VS1 is tied to CD1, CD2 is grounded */ | |
155 | + [IN_VS1 | IN_CD1_VS1H] = 0, /* ignore cardbay */ | |
156 | +}; | |
157 | + | |
158 | +/* | |
159 | + * poll hardware to check card insertion status | |
160 | + */ | |
161 | +static unsigned int __get_socket_status(struct bcm63xx_pcmcia_socket *skt) | |
162 | +{ | |
163 | + unsigned int stat; | |
164 | + u32 val; | |
165 | + | |
166 | + stat = 0; | |
167 | + | |
168 | + /* check CD for card presence */ | |
169 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
170 | + | |
171 | + if (!(val & PCMCIA_C1_CD1_MASK) && !(val & PCMCIA_C1_CD2_MASK)) | |
172 | + stat |= SS_DETECT; | |
173 | + | |
174 | + /* if new insertion, detect cardtype */ | |
175 | + if ((stat & SS_DETECT) && !skt->card_detected) { | |
176 | + unsigned int stat = 0; | |
177 | + | |
178 | + /* float VS1, float VS2 */ | |
179 | + val |= PCMCIA_C1_VS1OE_MASK; | |
180 | + val |= PCMCIA_C1_VS2OE_MASK; | |
181 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
182 | + | |
183 | + /* wait for output to stabilize and read VS[12] */ | |
184 | + udelay(10); | |
185 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
186 | + stat |= (val & PCMCIA_C1_VS1_MASK) ? IN_VS1 : 0; | |
187 | + stat |= (val & PCMCIA_C1_VS2_MASK) ? IN_VS2 : 0; | |
188 | + | |
189 | + /* drive VS1 low, float VS2 */ | |
190 | + val &= ~PCMCIA_C1_VS1OE_MASK; | |
191 | + val |= PCMCIA_C1_VS2OE_MASK; | |
192 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
193 | + | |
194 | + /* wait for output to stabilize and read CD[12] */ | |
195 | + udelay(10); | |
196 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
197 | + stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS2H : 0; | |
198 | + stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS2H : 0; | |
199 | + | |
200 | + /* float VS1, drive VS2 low */ | |
201 | + val |= PCMCIA_C1_VS1OE_MASK; | |
202 | + val &= ~PCMCIA_C1_VS2OE_MASK; | |
203 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
204 | + | |
205 | + /* wait for output to stabilize and read CD[12] */ | |
206 | + udelay(10); | |
207 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
208 | + stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS1H : 0; | |
209 | + stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS1H : 0; | |
210 | + | |
211 | + /* guess cardtype from all this */ | |
212 | + skt->card_type = vscd_to_cardtype[stat]; | |
213 | + if (!skt->card_type) | |
214 | + dev_err(&skt->socket.dev, "unsupported card type\n"); | |
215 | + | |
216 | + /* drive both VS pin to 0 again */ | |
217 | + val &= ~(PCMCIA_C1_VS1OE_MASK | PCMCIA_C1_VS2OE_MASK); | |
218 | + | |
219 | + /* enable correct logic */ | |
220 | + val &= ~(PCMCIA_C1_EN_PCMCIA_MASK | PCMCIA_C1_EN_CARDBUS_MASK); | |
221 | + if (skt->card_type & CARD_PCCARD) | |
222 | + val |= PCMCIA_C1_EN_PCMCIA_MASK; | |
223 | + else | |
224 | + val |= PCMCIA_C1_EN_CARDBUS_MASK; | |
225 | + | |
226 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
227 | + } | |
228 | + skt->card_detected = (stat & SS_DETECT) ? 1 : 0; | |
229 | + | |
230 | + /* report card type/voltage */ | |
231 | + if (skt->card_type & CARD_CARDBUS) | |
232 | + stat |= SS_CARDBUS; | |
233 | + if (skt->card_type & CARD_3V) | |
234 | + stat |= SS_3VCARD; | |
235 | + if (skt->card_type & CARD_XV) | |
236 | + stat |= SS_XVCARD; | |
237 | + stat |= SS_POWERON; | |
238 | + | |
239 | + if (gpio_get_value(skt->pd->ready_gpio)) | |
240 | + stat |= SS_READY; | |
241 | + | |
242 | + return stat; | |
243 | +} | |
244 | + | |
245 | +/* | |
246 | + * core request to get current socket status | |
247 | + */ | |
248 | +static int bcm63xx_pcmcia_get_status(struct pcmcia_socket *sock, | |
249 | + unsigned int *status) | |
250 | +{ | |
251 | + struct bcm63xx_pcmcia_socket *skt; | |
252 | + | |
253 | + skt = sock->driver_data; | |
254 | + | |
255 | + spin_lock_bh(&skt->lock); | |
256 | + *status = __get_socket_status(skt); | |
257 | + spin_unlock_bh(&skt->lock); | |
258 | + | |
259 | + return 0; | |
260 | +} | |
261 | + | |
262 | +/* | |
263 | + * socket polling timer callback | |
264 | + */ | |
265 | +static void bcm63xx_pcmcia_poll(unsigned long data) | |
266 | +{ | |
267 | + struct bcm63xx_pcmcia_socket *skt; | |
268 | + unsigned int stat, events; | |
269 | + | |
270 | + skt = (struct bcm63xx_pcmcia_socket *)data; | |
271 | + | |
272 | + spin_lock_bh(&skt->lock); | |
273 | + | |
274 | + stat = __get_socket_status(skt); | |
275 | + | |
276 | + /* keep only changed bits, and mask with required one from the | |
277 | + * core */ | |
278 | + events = (stat ^ skt->old_status) & skt->requested_state.csc_mask; | |
279 | + skt->old_status = stat; | |
280 | + spin_unlock_bh(&skt->lock); | |
281 | + | |
282 | + if (events) | |
283 | + pcmcia_parse_events(&skt->socket, events); | |
284 | + | |
285 | + mod_timer(&skt->timer, | |
286 | + jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE)); | |
287 | +} | |
288 | + | |
289 | +static int bcm63xx_pcmcia_set_io_map(struct pcmcia_socket *sock, | |
290 | + struct pccard_io_map *map) | |
291 | +{ | |
292 | + /* this doesn't seem to be called by pcmcia layer if static | |
293 | + * mapping is used */ | |
294 | + return 0; | |
295 | +} | |
296 | + | |
297 | +static int bcm63xx_pcmcia_set_mem_map(struct pcmcia_socket *sock, | |
298 | + struct pccard_mem_map *map) | |
299 | +{ | |
300 | + struct bcm63xx_pcmcia_socket *skt; | |
301 | + struct resource *res; | |
302 | + | |
303 | + skt = sock->driver_data; | |
304 | + if (map->flags & MAP_ATTRIB) | |
305 | + res = skt->attr_res; | |
306 | + else | |
307 | + res = skt->common_res; | |
308 | + | |
309 | + map->static_start = res->start + map->card_start; | |
310 | + return 0; | |
311 | +} | |
312 | + | |
313 | +static struct pccard_operations bcm63xx_pcmcia_operations = { | |
314 | + .init = bcm63xx_pcmcia_sock_init, | |
315 | + .suspend = bcm63xx_pcmcia_suspend, | |
316 | + .get_status = bcm63xx_pcmcia_get_status, | |
317 | + .set_socket = bcm63xx_pcmcia_set_socket, | |
318 | + .set_io_map = bcm63xx_pcmcia_set_io_map, | |
319 | + .set_mem_map = bcm63xx_pcmcia_set_mem_map, | |
320 | +}; | |
321 | + | |
322 | +/* | |
323 | + * register pcmcia socket to core | |
324 | + */ | |
325 | +static int __devinit bcm63xx_drv_pcmcia_probe(struct platform_device *pdev) | |
326 | +{ | |
327 | + struct bcm63xx_pcmcia_socket *skt; | |
328 | + struct pcmcia_socket *sock; | |
329 | + struct resource *res, *irq_res; | |
330 | + unsigned int regmem_size = 0, iomem_size = 0; | |
331 | + u32 val; | |
332 | + int ret; | |
333 | + | |
334 | + skt = kzalloc(sizeof(*skt), GFP_KERNEL); | |
335 | + if (!skt) | |
336 | + return -ENOMEM; | |
337 | + spin_lock_init(&skt->lock); | |
338 | + sock = &skt->socket; | |
339 | + sock->driver_data = skt; | |
340 | + | |
341 | + /* make sure we have all resources we need */ | |
342 | + skt->common_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
343 | + skt->attr_res = platform_get_resource(pdev, IORESOURCE_MEM, 2); | |
344 | + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
345 | + skt->pd = pdev->dev.platform_data; | |
346 | + if (!skt->common_res || !skt->attr_res || !irq_res || !skt->pd) { | |
347 | + ret = -EINVAL; | |
348 | + goto err; | |
349 | + } | |
350 | + | |
351 | + /* remap pcmcia registers */ | |
352 | + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
353 | + regmem_size = resource_size(res); | |
354 | + if (!request_mem_region(res->start, regmem_size, "bcm63xx_pcmcia")) { | |
355 | + ret = -EINVAL; | |
356 | + goto err; | |
357 | + } | |
358 | + skt->reg_res = res; | |
359 | + | |
360 | + skt->base = ioremap(res->start, regmem_size); | |
361 | + if (!skt->base) { | |
362 | + ret = -ENOMEM; | |
363 | + goto err; | |
364 | + } | |
365 | + | |
366 | + /* remap io registers */ | |
367 | + res = platform_get_resource(pdev, IORESOURCE_MEM, 3); | |
368 | + iomem_size = resource_size(res); | |
369 | + skt->io_base = ioremap(res->start, iomem_size); | |
370 | + if (!skt->io_base) { | |
371 | + ret = -ENOMEM; | |
372 | + goto err; | |
373 | + } | |
374 | + | |
375 | + /* resources are static */ | |
376 | + sock->resource_ops = &pccard_static_ops; | |
377 | + sock->ops = &bcm63xx_pcmcia_operations; | |
378 | + sock->owner = THIS_MODULE; | |
379 | + sock->dev.parent = &pdev->dev; | |
380 | + sock->features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD; | |
381 | + sock->io_offset = (unsigned long)skt->io_base; | |
382 | + sock->pci_irq = irq_res->start; | |
383 | + | |
384 | +#ifdef CONFIG_CARDBUS | |
385 | + sock->cb_dev = bcm63xx_cb_dev; | |
386 | + if (bcm63xx_cb_dev) | |
387 | + sock->features |= SS_CAP_CARDBUS; | |
388 | +#endif | |
389 | + | |
390 | + /* assume common & attribute memory have the same size */ | |
391 | + sock->map_size = resource_size(skt->common_res); | |
392 | + | |
393 | + /* initialize polling timer */ | |
394 | + setup_timer(&skt->timer, bcm63xx_pcmcia_poll, (unsigned long)skt); | |
395 | + | |
396 | + /* initialize pcmcia control register, drive VS[12] to 0, | |
397 | + * leave CB IDSEL to the old value since it is set by the PCI | |
398 | + * layer */ | |
399 | + val = pcmcia_readl(skt, PCMCIA_C1_REG); | |
400 | + val &= PCMCIA_C1_CBIDSEL_MASK; | |
401 | + val |= PCMCIA_C1_EN_PCMCIA_GPIO_MASK; | |
402 | + pcmcia_writel(skt, val, PCMCIA_C1_REG); | |
403 | + | |
404 | + /* | |
405 | + * Hardware has only one set of timings registers, not one for | |
406 | + * each memory access type, so we configure them for the | |
407 | + * slowest one: attribute memory. | |
408 | + */ | |
409 | + val = PCMCIA_C2_DATA16_MASK; | |
410 | + val |= 10 << PCMCIA_C2_RWCOUNT_SHIFT; | |
411 | + val |= 6 << PCMCIA_C2_INACTIVE_SHIFT; | |
412 | + val |= 3 << PCMCIA_C2_SETUP_SHIFT; | |
413 | + val |= 3 << PCMCIA_C2_HOLD_SHIFT; | |
414 | + pcmcia_writel(skt, val, PCMCIA_C2_REG); | |
415 | + | |
416 | + ret = pcmcia_register_socket(sock); | |
417 | + if (ret) | |
418 | + goto err; | |
419 | + | |
420 | + /* start polling socket */ | |
421 | + mod_timer(&skt->timer, | |
422 | + jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE)); | |
423 | + | |
424 | + platform_set_drvdata(pdev, skt); | |
425 | + return 0; | |
426 | + | |
427 | +err: | |
428 | + if (skt->io_base) | |
429 | + iounmap(skt->io_base); | |
430 | + if (skt->base) | |
431 | + iounmap(skt->base); | |
432 | + if (skt->reg_res) | |
433 | + release_mem_region(skt->reg_res->start, regmem_size); | |
434 | + kfree(skt); | |
435 | + return ret; | |
436 | +} | |
437 | + | |
438 | +static int __devexit bcm63xx_drv_pcmcia_remove(struct platform_device *pdev) | |
439 | +{ | |
440 | + struct bcm63xx_pcmcia_socket *skt; | |
441 | + struct resource *res; | |
442 | + | |
443 | + skt = platform_get_drvdata(pdev); | |
444 | + del_timer_sync(&skt->timer); | |
445 | + iounmap(skt->base); | |
446 | + iounmap(skt->io_base); | |
447 | + res = skt->reg_res; | |
448 | + release_mem_region(res->start, resource_size(res)); | |
449 | + kfree(skt); | |
450 | + return 0; | |
451 | +} | |
452 | + | |
453 | +struct platform_driver bcm63xx_pcmcia_driver = { | |
454 | + .probe = bcm63xx_drv_pcmcia_probe, | |
455 | + .remove = __devexit_p(bcm63xx_drv_pcmcia_remove), | |
456 | + .driver = { | |
457 | + .name = "bcm63xx_pcmcia", | |
458 | + .owner = THIS_MODULE, | |
459 | + }, | |
460 | +}; | |
461 | + | |
462 | +#ifdef CONFIG_CARDBUS | |
463 | +static int __devinit bcm63xx_cb_probe(struct pci_dev *dev, | |
464 | + const struct pci_device_id *id) | |
465 | +{ | |
466 | + /* keep pci device */ | |
467 | + bcm63xx_cb_dev = dev; | |
468 | + return platform_driver_register(&bcm63xx_pcmcia_driver); | |
469 | +} | |
470 | + | |
471 | +static void __devexit bcm63xx_cb_exit(struct pci_dev *dev) | |
472 | +{ | |
473 | + platform_driver_unregister(&bcm63xx_pcmcia_driver); | |
474 | + bcm63xx_cb_dev = NULL; | |
475 | +} | |
476 | + | |
477 | +static struct pci_device_id bcm63xx_cb_table[] = { | |
478 | + { | |
479 | + .vendor = PCI_VENDOR_ID_BROADCOM, | |
480 | + .device = BCM6348_CPU_ID, | |
481 | + .subvendor = PCI_VENDOR_ID_BROADCOM, | |
482 | + .subdevice = PCI_ANY_ID, | |
483 | + .class = PCI_CLASS_BRIDGE_CARDBUS << 8, | |
484 | + .class_mask = ~0, | |
485 | + }, | |
486 | + | |
487 | + { | |
488 | + .vendor = PCI_VENDOR_ID_BROADCOM, | |
489 | + .device = BCM6358_CPU_ID, | |
490 | + .subvendor = PCI_VENDOR_ID_BROADCOM, | |
491 | + .subdevice = PCI_ANY_ID, | |
492 | + .class = PCI_CLASS_BRIDGE_CARDBUS << 8, | |
493 | + .class_mask = ~0, | |
494 | + }, | |
495 | + | |
496 | + { }, | |
497 | +}; | |
498 | + | |
499 | +MODULE_DEVICE_TABLE(pci, bcm63xx_cb_table); | |
500 | + | |
501 | +static struct pci_driver bcm63xx_cardbus_driver = { | |
502 | + .name = "bcm63xx_cardbus", | |
503 | + .id_table = bcm63xx_cb_table, | |
504 | + .probe = bcm63xx_cb_probe, | |
505 | + .remove = __devexit_p(bcm63xx_cb_exit), | |
506 | +}; | |
507 | +#endif | |
508 | + | |
509 | +/* | |
510 | + * if cardbus support is enabled, register our platform device after | |
511 | + * our fake cardbus bridge has been registered | |
512 | + */ | |
513 | +static int __init bcm63xx_pcmcia_init(void) | |
514 | +{ | |
515 | +#ifdef CONFIG_CARDBUS | |
516 | + return pci_register_driver(&bcm63xx_cardbus_driver); | |
517 | +#else | |
518 | + return platform_driver_register(&bcm63xx_pcmcia_driver); | |
519 | +#endif | |
520 | +} | |
521 | + | |
522 | +static void __exit bcm63xx_pcmcia_exit(void) | |
523 | +{ | |
524 | +#ifdef CONFIG_CARDBUS | |
525 | + return pci_unregister_driver(&bcm63xx_cardbus_driver); | |
526 | +#else | |
527 | + platform_driver_unregister(&bcm63xx_pcmcia_driver); | |
528 | +#endif | |
529 | +} | |
530 | + | |
531 | +module_init(bcm63xx_pcmcia_init); | |
532 | +module_exit(bcm63xx_pcmcia_exit); | |
533 | + | |
534 | +MODULE_LICENSE("GPL"); | |
535 | +MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>"); | |
536 | +MODULE_DESCRIPTION("Linux PCMCIA Card Services: bcm63xx Socket Controller"); |
drivers/pcmcia/bcm63xx_pcmcia.h
1 | +#ifndef BCM63XX_PCMCIA_H_ | |
2 | +#define BCM63XX_PCMCIA_H_ | |
3 | + | |
4 | +#include <linux/types.h> | |
5 | +#include <linux/timer.h> | |
6 | +#include <pcmcia/ss.h> | |
7 | +#include <bcm63xx_dev_pcmcia.h> | |
8 | + | |
9 | +/* socket polling rate in ms */ | |
10 | +#define BCM63XX_PCMCIA_POLL_RATE 500 | |
11 | + | |
12 | +enum { | |
13 | + CARD_CARDBUS = (1 << 0), | |
14 | + CARD_PCCARD = (1 << 1), | |
15 | + CARD_5V = (1 << 2), | |
16 | + CARD_3V = (1 << 3), | |
17 | + CARD_XV = (1 << 4), | |
18 | + CARD_YV = (1 << 5), | |
19 | +}; | |
20 | + | |
21 | +struct bcm63xx_pcmcia_socket { | |
22 | + struct pcmcia_socket socket; | |
23 | + | |
24 | + /* platform specific data */ | |
25 | + struct bcm63xx_pcmcia_platform_data *pd; | |
26 | + | |
27 | + /* all regs access are protected by this spinlock */ | |
28 | + spinlock_t lock; | |
29 | + | |
30 | + /* pcmcia registers resource */ | |
31 | + struct resource *reg_res; | |
32 | + | |
33 | + /* base remapped address of registers */ | |
34 | + void __iomem *base; | |
35 | + | |
36 | + /* whether a card is detected at the moment */ | |
37 | + int card_detected; | |
38 | + | |
39 | + /* type of detected card (mask of above enum) */ | |
40 | + u8 card_type; | |
41 | + | |
42 | + /* keep last socket status to implement event reporting */ | |
43 | + unsigned int old_status; | |
44 | + | |
45 | + /* backup of requested socket state */ | |
46 | + socket_state_t requested_state; | |
47 | + | |
48 | + /* timer used for socket status polling */ | |
49 | + struct timer_list timer; | |
50 | + | |
51 | + /* attribute/common memory resources */ | |
52 | + struct resource *attr_res; | |
53 | + struct resource *common_res; | |
54 | + struct resource *io_res; | |
55 | + | |
56 | + /* base address of io memory */ | |
57 | + void __iomem *io_base; | |
58 | +}; | |
59 | + | |
60 | +#endif /* BCM63XX_PCMCIA_H_ */ |