diff options
author | Christian Lamparter <chunkeey@web.de> | 2009-01-10 19:18:38 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-01-29 16:00:25 -0500 |
commit | cd8d3d321285a34b4e29cb7b04e552c49cc0f018 (patch) | |
tree | ca4960a84c0e5cd2cd5cd28d4e68b1cc5748d6ee /drivers/net | |
parent | 4628ae75583311fcbbd02f4eebcfc08514dfbd65 (diff) |
p54spi: p54spi driver
This patch adds the p54spi driver.
Signed-off-by: Christian Lamparter <chunkeey@web.de>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wireless/p54/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/wireless/p54/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/wireless/p54/p54.h | 9 | ||||
-rw-r--r-- | drivers/net/wireless/p54/p54common.c | 3 | ||||
-rw-r--r-- | drivers/net/wireless/p54/p54spi.c | 759 | ||||
-rw-r--r-- | drivers/net/wireless/p54/p54spi.h | 127 |
6 files changed, 908 insertions, 1 deletions
diff --git a/drivers/net/wireless/p54/Kconfig b/drivers/net/wireless/p54/Kconfig index d3469d08f966..cfc5f41aa136 100644 --- a/drivers/net/wireless/p54/Kconfig +++ b/drivers/net/wireless/p54/Kconfig | |||
@@ -61,3 +61,13 @@ config P54_PCI | |||
61 | http://prism54.org/ | 61 | http://prism54.org/ |
62 | 62 | ||
63 | If you choose to build a module, it'll be called p54pci. | 63 | If you choose to build a module, it'll be called p54pci. |
64 | |||
65 | config P54_SPI | ||
66 | tristate "Prism54 SPI (stlc45xx) support" | ||
67 | depends on P54_COMMON && SPI_MASTER | ||
68 | ---help--- | ||
69 | This driver is for stlc4550 or stlc4560 based wireless chips. | ||
70 | This driver is experimental, untested and will probably only work on | ||
71 | Nokia's N800/N810 Portable Internet Tablet. | ||
72 | |||
73 | If you choose to build a module, it'll be called p54spi. | ||
diff --git a/drivers/net/wireless/p54/Makefile b/drivers/net/wireless/p54/Makefile index 4fa9ce717360..c2050dee6293 100644 --- a/drivers/net/wireless/p54/Makefile +++ b/drivers/net/wireless/p54/Makefile | |||
@@ -1,3 +1,4 @@ | |||
1 | obj-$(CONFIG_P54_COMMON) += p54common.o | 1 | obj-$(CONFIG_P54_COMMON) += p54common.o |
2 | obj-$(CONFIG_P54_USB) += p54usb.o | 2 | obj-$(CONFIG_P54_USB) += p54usb.o |
3 | obj-$(CONFIG_P54_PCI) += p54pci.o | 3 | obj-$(CONFIG_P54_PCI) += p54pci.o |
4 | obj-$(CONFIG_P54_SPI) += p54spi.o | ||
diff --git a/drivers/net/wireless/p54/p54.h b/drivers/net/wireless/p54/p54.h index ac11efd19db2..64492feca9b2 100644 --- a/drivers/net/wireless/p54/p54.h +++ b/drivers/net/wireless/p54/p54.h | |||
@@ -104,6 +104,14 @@ struct p54_cal_database { | |||
104 | #define FW_LM87 0x4c4d3837 | 104 | #define FW_LM87 0x4c4d3837 |
105 | #define FW_LM20 0x4c4d3230 | 105 | #define FW_LM20 0x4c4d3230 |
106 | 106 | ||
107 | enum fw_state { | ||
108 | FW_STATE_OFF, | ||
109 | FW_STATE_BOOTING, | ||
110 | FW_STATE_READY, | ||
111 | FW_STATE_RESET, | ||
112 | FW_STATE_RESETTING, | ||
113 | }; | ||
114 | |||
107 | struct p54_common { | 115 | struct p54_common { |
108 | struct ieee80211_hw *hw; | 116 | struct ieee80211_hw *hw; |
109 | u32 rx_start; | 117 | u32 rx_start; |
@@ -154,6 +162,7 @@ struct p54_common { | |||
154 | int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb); | 162 | int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb); |
155 | void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb); | 163 | void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb); |
156 | int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw); | 164 | int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw); |
165 | int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len); | ||
157 | int p54_read_eeprom(struct ieee80211_hw *dev); | 166 | int p54_read_eeprom(struct ieee80211_hw *dev); |
158 | struct ieee80211_hw *p54_init_common(size_t priv_data_len); | 167 | struct ieee80211_hw *p54_init_common(size_t priv_data_len); |
159 | void p54_free_common(struct ieee80211_hw *dev); | 168 | void p54_free_common(struct ieee80211_hw *dev); |
diff --git a/drivers/net/wireless/p54/p54common.c b/drivers/net/wireless/p54/p54common.c index 9fc0c9efe701..45c2e7ad3acd 100644 --- a/drivers/net/wireless/p54/p54common.c +++ b/drivers/net/wireless/p54/p54common.c | |||
@@ -483,7 +483,7 @@ static struct p54_cal_database *p54_convert_db(struct pda_custom_wrapper *src, | |||
483 | return dst; | 483 | return dst; |
484 | } | 484 | } |
485 | 485 | ||
486 | static int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) | 486 | int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) |
487 | { | 487 | { |
488 | struct p54_common *priv = dev->priv; | 488 | struct p54_common *priv = dev->priv; |
489 | struct eeprom_pda_wrap *wrap = NULL; | 489 | struct eeprom_pda_wrap *wrap = NULL; |
@@ -698,6 +698,7 @@ static int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) | |||
698 | wiphy_name(dev->wiphy)); | 698 | wiphy_name(dev->wiphy)); |
699 | return err; | 699 | return err; |
700 | } | 700 | } |
701 | EXPORT_SYMBOL_GPL(p54_parse_eeprom); | ||
701 | 702 | ||
702 | static int p54_rssi_to_dbm(struct ieee80211_hw *dev, int rssi) | 703 | static int p54_rssi_to_dbm(struct ieee80211_hw *dev, int rssi) |
703 | { | 704 | { |
diff --git a/drivers/net/wireless/p54/p54spi.c b/drivers/net/wireless/p54/p54spi.c new file mode 100644 index 000000000000..b8dede741ef6 --- /dev/null +++ b/drivers/net/wireless/p54/p54spi.c | |||
@@ -0,0 +1,759 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2008 Christian Lamparter <chunkeey@web.de> | ||
3 | * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> | ||
4 | * | ||
5 | * This driver is a port from stlc45xx: | ||
6 | * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
20 | * 02110-1301 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/interrupt.h> | ||
26 | #include <linux/firmware.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/irq.h> | ||
29 | #include <linux/spi/spi.h> | ||
30 | #include <linux/etherdevice.h> | ||
31 | #include <linux/gpio.h> | ||
32 | |||
33 | #include "p54spi.h" | ||
34 | #include "p54spi_eeprom.h" | ||
35 | #include "p54.h" | ||
36 | |||
37 | #include "p54common.h" | ||
38 | |||
39 | MODULE_FIRMWARE("3826.arm"); | ||
40 | MODULE_ALIAS("stlc45xx"); | ||
41 | |||
42 | static void p54spi_spi_read(struct p54s_priv *priv, u8 address, | ||
43 | void *buf, size_t len) | ||
44 | { | ||
45 | struct spi_transfer t[2]; | ||
46 | struct spi_message m; | ||
47 | __le16 addr; | ||
48 | |||
49 | /* We first push the address */ | ||
50 | addr = cpu_to_le16(address << 8 | SPI_ADRS_READ_BIT_15); | ||
51 | |||
52 | spi_message_init(&m); | ||
53 | memset(t, 0, sizeof(t)); | ||
54 | |||
55 | t[0].tx_buf = &addr; | ||
56 | t[0].len = sizeof(addr); | ||
57 | spi_message_add_tail(&t[0], &m); | ||
58 | |||
59 | t[1].rx_buf = buf; | ||
60 | t[1].len = len; | ||
61 | spi_message_add_tail(&t[1], &m); | ||
62 | |||
63 | spi_sync(priv->spi, &m); | ||
64 | } | ||
65 | |||
66 | |||
67 | static void p54spi_spi_write(struct p54s_priv *priv, u8 address, | ||
68 | const void *buf, size_t len) | ||
69 | { | ||
70 | struct spi_transfer t[3]; | ||
71 | struct spi_message m; | ||
72 | __le16 addr; | ||
73 | |||
74 | /* We first push the address */ | ||
75 | addr = cpu_to_le16(address << 8); | ||
76 | |||
77 | spi_message_init(&m); | ||
78 | memset(t, 0, sizeof(t)); | ||
79 | |||
80 | t[0].tx_buf = &addr; | ||
81 | t[0].len = sizeof(addr); | ||
82 | spi_message_add_tail(&t[0], &m); | ||
83 | |||
84 | t[1].tx_buf = buf; | ||
85 | t[1].len = len; | ||
86 | spi_message_add_tail(&t[1], &m); | ||
87 | |||
88 | if (len % 2) { | ||
89 | __le16 last_word; | ||
90 | last_word = cpu_to_le16(((u8 *)buf)[len - 1]); | ||
91 | |||
92 | t[2].tx_buf = &last_word; | ||
93 | t[2].len = sizeof(last_word); | ||
94 | spi_message_add_tail(&t[2], &m); | ||
95 | } | ||
96 | |||
97 | spi_sync(priv->spi, &m); | ||
98 | } | ||
99 | |||
100 | static u16 p54spi_read16(struct p54s_priv *priv, u8 addr) | ||
101 | { | ||
102 | __le16 val; | ||
103 | |||
104 | p54spi_spi_read(priv, addr, &val, sizeof(val)); | ||
105 | |||
106 | return le16_to_cpu(val); | ||
107 | } | ||
108 | |||
109 | static u32 p54spi_read32(struct p54s_priv *priv, u8 addr) | ||
110 | { | ||
111 | __le32 val; | ||
112 | |||
113 | p54spi_spi_read(priv, addr, &val, sizeof(val)); | ||
114 | |||
115 | return le32_to_cpu(val); | ||
116 | } | ||
117 | |||
118 | static inline void p54spi_write16(struct p54s_priv *priv, u8 addr, __le16 val) | ||
119 | { | ||
120 | p54spi_spi_write(priv, addr, &val, sizeof(val)); | ||
121 | } | ||
122 | |||
123 | static inline void p54spi_write32(struct p54s_priv *priv, u8 addr, __le32 val) | ||
124 | { | ||
125 | p54spi_spi_write(priv, addr, &val, sizeof(val)); | ||
126 | } | ||
127 | |||
128 | struct p54spi_spi_reg { | ||
129 | u16 address; /* __le16 ? */ | ||
130 | u16 length; | ||
131 | char *name; | ||
132 | }; | ||
133 | |||
134 | static const struct p54spi_spi_reg p54spi_registers_array[] = | ||
135 | { | ||
136 | { SPI_ADRS_ARM_INTERRUPTS, 32, "ARM_INT " }, | ||
137 | { SPI_ADRS_ARM_INT_EN, 32, "ARM_INT_ENA " }, | ||
138 | { SPI_ADRS_HOST_INTERRUPTS, 32, "HOST_INT " }, | ||
139 | { SPI_ADRS_HOST_INT_EN, 32, "HOST_INT_ENA" }, | ||
140 | { SPI_ADRS_HOST_INT_ACK, 32, "HOST_INT_ACK" }, | ||
141 | { SPI_ADRS_GEN_PURP_1, 32, "GP1_COMM " }, | ||
142 | { SPI_ADRS_GEN_PURP_2, 32, "GP2_COMM " }, | ||
143 | { SPI_ADRS_DEV_CTRL_STAT, 32, "DEV_CTRL_STA" }, | ||
144 | { SPI_ADRS_DMA_DATA, 16, "DMA_DATA " }, | ||
145 | { SPI_ADRS_DMA_WRITE_CTRL, 16, "DMA_WR_CTRL " }, | ||
146 | { SPI_ADRS_DMA_WRITE_LEN, 16, "DMA_WR_LEN " }, | ||
147 | { SPI_ADRS_DMA_WRITE_BASE, 32, "DMA_WR_BASE " }, | ||
148 | { SPI_ADRS_DMA_READ_CTRL, 16, "DMA_RD_CTRL " }, | ||
149 | { SPI_ADRS_DMA_READ_LEN, 16, "DMA_RD_LEN " }, | ||
150 | { SPI_ADRS_DMA_WRITE_BASE, 32, "DMA_RD_BASE " } | ||
151 | }; | ||
152 | |||
153 | static int p54spi_wait_bit(struct p54s_priv *priv, u16 reg, __le32 bits) | ||
154 | { | ||
155 | int i; | ||
156 | __le32 buffer; | ||
157 | |||
158 | for (i = 0; i < 2000; i++) { | ||
159 | p54spi_spi_read(priv, reg, &buffer, sizeof(buffer)); | ||
160 | if (buffer == bits) | ||
161 | return 1; | ||
162 | |||
163 | msleep(1); | ||
164 | } | ||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static int p54spi_request_firmware(struct ieee80211_hw *dev) | ||
169 | { | ||
170 | struct p54s_priv *priv = dev->priv; | ||
171 | int ret; | ||
172 | |||
173 | /* FIXME: should driver use it's own struct device? */ | ||
174 | ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev); | ||
175 | |||
176 | if (ret < 0) { | ||
177 | dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret); | ||
178 | return ret; | ||
179 | } | ||
180 | |||
181 | ret = p54_parse_firmware(dev, priv->firmware); | ||
182 | if (ret) { | ||
183 | release_firmware(priv->firmware); | ||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int p54spi_request_eeprom(struct ieee80211_hw *dev) | ||
191 | { | ||
192 | struct p54s_priv *priv = dev->priv; | ||
193 | const struct firmware *eeprom; | ||
194 | int ret; | ||
195 | |||
196 | /* | ||
197 | * allow users to customize their eeprom. | ||
198 | */ | ||
199 | |||
200 | ret = request_firmware(&eeprom, "3826.eeprom", &priv->spi->dev); | ||
201 | if (ret < 0) { | ||
202 | dev_info(&priv->spi->dev, "loading default eeprom...\n"); | ||
203 | ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom, | ||
204 | sizeof(p54spi_eeprom)); | ||
205 | } else { | ||
206 | dev_info(&priv->spi->dev, "loading user eeprom...\n"); | ||
207 | ret = p54_parse_eeprom(dev, (void *) eeprom->data, | ||
208 | (int)eeprom->size); | ||
209 | release_firmware(eeprom); | ||
210 | } | ||
211 | return ret; | ||
212 | } | ||
213 | |||
214 | static int p54spi_upload_firmware(struct ieee80211_hw *dev) | ||
215 | { | ||
216 | struct p54s_priv *priv = dev->priv; | ||
217 | unsigned long fw_len, fw_addr; | ||
218 | long _fw_len; | ||
219 | |||
220 | /* stop the device */ | ||
221 | p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( | ||
222 | SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET | | ||
223 | SPI_CTRL_STAT_START_HALTED)); | ||
224 | |||
225 | msleep(TARGET_BOOT_SLEEP); | ||
226 | |||
227 | p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( | ||
228 | SPI_CTRL_STAT_HOST_OVERRIDE | | ||
229 | SPI_CTRL_STAT_START_HALTED)); | ||
230 | |||
231 | msleep(TARGET_BOOT_SLEEP); | ||
232 | |||
233 | fw_addr = ISL38XX_DEV_FIRMWARE_ADDR; | ||
234 | fw_len = priv->firmware->size; | ||
235 | |||
236 | while (fw_len > 0) { | ||
237 | _fw_len = min_t(long, fw_len, SPI_MAX_PACKET_SIZE); | ||
238 | |||
239 | p54spi_write16(priv, SPI_ADRS_DMA_WRITE_CTRL, | ||
240 | cpu_to_le16(SPI_DMA_WRITE_CTRL_ENABLE)); | ||
241 | |||
242 | if (p54spi_wait_bit(priv, SPI_ADRS_DMA_WRITE_CTRL, | ||
243 | cpu_to_le32(HOST_ALLOWED)) == 0) { | ||
244 | dev_err(&priv->spi->dev, "fw_upload not allowed " | ||
245 | "to DMA write."); | ||
246 | return -EAGAIN; | ||
247 | } | ||
248 | |||
249 | p54spi_write16(priv, SPI_ADRS_DMA_WRITE_LEN, | ||
250 | cpu_to_le16(_fw_len)); | ||
251 | p54spi_write32(priv, SPI_ADRS_DMA_WRITE_BASE, | ||
252 | cpu_to_le32(fw_addr)); | ||
253 | |||
254 | p54spi_spi_write(priv, SPI_ADRS_DMA_DATA, | ||
255 | &priv->firmware->data, _fw_len); | ||
256 | |||
257 | fw_len -= _fw_len; | ||
258 | fw_addr += _fw_len; | ||
259 | |||
260 | /* FIXME: I think this doesn't work if firmware is large, | ||
261 | * this loop goes to second round. fw->data is not | ||
262 | * increased at all! */ | ||
263 | } | ||
264 | |||
265 | BUG_ON(fw_len != 0); | ||
266 | |||
267 | /* enable host interrupts */ | ||
268 | p54spi_write32(priv, SPI_ADRS_HOST_INT_EN, | ||
269 | cpu_to_le32(SPI_HOST_INTS_DEFAULT)); | ||
270 | |||
271 | /* boot the device */ | ||
272 | p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( | ||
273 | SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET | | ||
274 | SPI_CTRL_STAT_RAM_BOOT)); | ||
275 | |||
276 | msleep(TARGET_BOOT_SLEEP); | ||
277 | |||
278 | p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( | ||
279 | SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_RAM_BOOT)); | ||
280 | msleep(TARGET_BOOT_SLEEP); | ||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | static void p54spi_power_off(struct p54s_priv *priv) | ||
285 | { | ||
286 | disable_irq(gpio_to_irq(priv->config->irq_gpio)); | ||
287 | gpio_set_value(priv->config->power_gpio, 0); | ||
288 | } | ||
289 | |||
290 | static void p54spi_power_on(struct p54s_priv *priv) | ||
291 | { | ||
292 | gpio_set_value(priv->config->power_gpio, 1); | ||
293 | enable_irq(gpio_to_irq(priv->config->irq_gpio)); | ||
294 | |||
295 | /* | ||
296 | * need to wait a while before device can be accessed, the lenght | ||
297 | * is just a guess | ||
298 | */ | ||
299 | msleep(10); | ||
300 | } | ||
301 | |||
302 | static inline void p54spi_int_ack(struct p54s_priv *priv, u32 val) | ||
303 | { | ||
304 | p54spi_write32(priv, SPI_ADRS_HOST_INT_ACK, cpu_to_le32(val)); | ||
305 | } | ||
306 | |||
307 | static void p54spi_wakeup(struct p54s_priv *priv) | ||
308 | { | ||
309 | unsigned long timeout; | ||
310 | u32 ints; | ||
311 | |||
312 | /* wake the chip */ | ||
313 | p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS, | ||
314 | cpu_to_le32(SPI_TARGET_INT_WAKEUP)); | ||
315 | |||
316 | /* And wait for the READY interrupt */ | ||
317 | timeout = jiffies + HZ; | ||
318 | |||
319 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
320 | while (!(ints & SPI_HOST_INT_READY)) { | ||
321 | if (time_after(jiffies, timeout)) | ||
322 | goto out; | ||
323 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
324 | } | ||
325 | |||
326 | p54spi_int_ack(priv, SPI_HOST_INT_READY); | ||
327 | |||
328 | out: | ||
329 | return; | ||
330 | } | ||
331 | |||
332 | static inline void p54spi_sleep(struct p54s_priv *priv) | ||
333 | { | ||
334 | p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS, | ||
335 | cpu_to_le32(SPI_TARGET_INT_SLEEP)); | ||
336 | } | ||
337 | |||
338 | static void p54spi_int_ready(struct p54s_priv *priv) | ||
339 | { | ||
340 | p54spi_write32(priv, SPI_ADRS_HOST_INT_EN, cpu_to_le32( | ||
341 | SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE)); | ||
342 | |||
343 | switch (priv->fw_state) { | ||
344 | case FW_STATE_BOOTING: | ||
345 | priv->fw_state = FW_STATE_READY; | ||
346 | complete(&priv->fw_comp); | ||
347 | break; | ||
348 | case FW_STATE_RESETTING: | ||
349 | priv->fw_state = FW_STATE_READY; | ||
350 | /* TODO: reinitialize state */ | ||
351 | break; | ||
352 | default: | ||
353 | break; | ||
354 | } | ||
355 | } | ||
356 | |||
357 | static int p54spi_rx(struct p54s_priv *priv) | ||
358 | { | ||
359 | struct sk_buff *skb; | ||
360 | u16 len; | ||
361 | |||
362 | p54spi_wakeup(priv); | ||
363 | |||
364 | /* dummy read to flush SPI DMA controller bug */ | ||
365 | p54spi_read16(priv, SPI_ADRS_GEN_PURP_1); | ||
366 | |||
367 | len = p54spi_read16(priv, SPI_ADRS_DMA_DATA); | ||
368 | |||
369 | if (len == 0) { | ||
370 | dev_err(&priv->spi->dev, "rx request of zero bytes"); | ||
371 | return 0; | ||
372 | } | ||
373 | |||
374 | skb = dev_alloc_skb(len); | ||
375 | if (!skb) { | ||
376 | dev_err(&priv->spi->dev, "could not alloc skb"); | ||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | p54spi_spi_read(priv, SPI_ADRS_DMA_DATA, skb_put(skb, len), len); | ||
381 | p54spi_sleep(priv); | ||
382 | |||
383 | if (p54_rx(priv->hw, skb) == 0) | ||
384 | dev_kfree_skb(skb); | ||
385 | |||
386 | return 0; | ||
387 | } | ||
388 | |||
389 | |||
390 | static irqreturn_t p54spi_interrupt(int irq, void *config) | ||
391 | { | ||
392 | struct spi_device *spi = config; | ||
393 | struct p54s_priv *priv = dev_get_drvdata(&spi->dev); | ||
394 | |||
395 | queue_work(priv->hw->workqueue, &priv->work); | ||
396 | |||
397 | return IRQ_HANDLED; | ||
398 | } | ||
399 | |||
400 | static int p54spi_tx_frame(struct p54s_priv *priv, struct sk_buff *skb) | ||
401 | { | ||
402 | struct p54_hdr *hdr = (struct p54_hdr *) skb->data; | ||
403 | struct p54s_dma_regs dma_regs; | ||
404 | unsigned long timeout; | ||
405 | int ret = 0; | ||
406 | u32 ints; | ||
407 | |||
408 | p54spi_wakeup(priv); | ||
409 | |||
410 | dma_regs.cmd = cpu_to_le16(SPI_DMA_WRITE_CTRL_ENABLE); | ||
411 | dma_regs.len = cpu_to_le16(skb->len); | ||
412 | dma_regs.addr = hdr->req_id; | ||
413 | |||
414 | p54spi_spi_write(priv, SPI_ADRS_DMA_WRITE_CTRL, &dma_regs, | ||
415 | sizeof(dma_regs)); | ||
416 | |||
417 | p54spi_spi_write(priv, SPI_ADRS_DMA_DATA, skb->data, skb->len); | ||
418 | |||
419 | timeout = jiffies + 2 * HZ; | ||
420 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
421 | while (!(ints & SPI_HOST_INT_WR_READY)) { | ||
422 | if (time_after(jiffies, timeout)) { | ||
423 | dev_err(&priv->spi->dev, "WR_READY timeout"); | ||
424 | ret = -1; | ||
425 | goto out; | ||
426 | } | ||
427 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
428 | } | ||
429 | |||
430 | p54spi_int_ack(priv, SPI_HOST_INT_WR_READY); | ||
431 | p54spi_sleep(priv); | ||
432 | |||
433 | out: | ||
434 | if (FREE_AFTER_TX(skb)) | ||
435 | p54_free_skb(priv->hw, skb); | ||
436 | return ret; | ||
437 | } | ||
438 | |||
439 | static int p54spi_wq_tx(struct p54s_priv *priv) | ||
440 | { | ||
441 | struct p54s_tx_info *entry; | ||
442 | struct sk_buff *skb; | ||
443 | struct ieee80211_tx_info *info; | ||
444 | struct p54_tx_info *minfo; | ||
445 | struct p54s_tx_info *dinfo; | ||
446 | int ret = 0; | ||
447 | |||
448 | spin_lock_bh(&priv->tx_lock); | ||
449 | |||
450 | while (!list_empty(&priv->tx_pending)) { | ||
451 | entry = list_entry(priv->tx_pending.next, | ||
452 | struct p54s_tx_info, tx_list); | ||
453 | |||
454 | list_del_init(&entry->tx_list); | ||
455 | |||
456 | spin_unlock_bh(&priv->tx_lock); | ||
457 | |||
458 | dinfo = container_of((void *) entry, struct p54s_tx_info, | ||
459 | tx_list); | ||
460 | minfo = container_of((void *) dinfo, struct p54_tx_info, | ||
461 | data); | ||
462 | info = container_of((void *) minfo, struct ieee80211_tx_info, | ||
463 | rate_driver_data); | ||
464 | skb = container_of((void *) info, struct sk_buff, cb); | ||
465 | |||
466 | ret = p54spi_tx_frame(priv, skb); | ||
467 | |||
468 | spin_lock_bh(&priv->tx_lock); | ||
469 | |||
470 | if (ret < 0) { | ||
471 | p54_free_skb(priv->hw, skb); | ||
472 | goto out; | ||
473 | } | ||
474 | } | ||
475 | |||
476 | out: | ||
477 | spin_unlock_bh(&priv->tx_lock); | ||
478 | return ret; | ||
479 | } | ||
480 | |||
481 | static void p54spi_op_tx(struct ieee80211_hw *dev, struct sk_buff *skb) | ||
482 | { | ||
483 | struct p54s_priv *priv = dev->priv; | ||
484 | struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | ||
485 | struct p54_tx_info *mi = (struct p54_tx_info *) info->rate_driver_data; | ||
486 | struct p54s_tx_info *di = (struct p54s_tx_info *) mi->data; | ||
487 | |||
488 | BUILD_BUG_ON(sizeof(*di) > sizeof((mi->data))); | ||
489 | |||
490 | spin_lock_bh(&priv->tx_lock); | ||
491 | list_add_tail(&di->tx_list, &priv->tx_pending); | ||
492 | spin_unlock_bh(&priv->tx_lock); | ||
493 | |||
494 | queue_work(priv->hw->workqueue, &priv->work); | ||
495 | } | ||
496 | |||
497 | static void p54spi_work(struct work_struct *work) | ||
498 | { | ||
499 | struct p54s_priv *priv = container_of(work, struct p54s_priv, work); | ||
500 | u32 ints; | ||
501 | int ret; | ||
502 | |||
503 | mutex_lock(&priv->mutex); | ||
504 | |||
505 | if (priv->fw_state == FW_STATE_OFF && | ||
506 | priv->fw_state == FW_STATE_RESET) | ||
507 | goto out; | ||
508 | |||
509 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
510 | |||
511 | if (ints & SPI_HOST_INT_READY) { | ||
512 | p54spi_int_ready(priv); | ||
513 | p54spi_int_ack(priv, SPI_HOST_INT_READY); | ||
514 | } | ||
515 | |||
516 | if (priv->fw_state != FW_STATE_READY) | ||
517 | goto out; | ||
518 | |||
519 | if (ints & SPI_HOST_INT_UPDATE) { | ||
520 | p54spi_int_ack(priv, SPI_HOST_INT_UPDATE); | ||
521 | ret = p54spi_rx(priv); | ||
522 | if (ret < 0) | ||
523 | goto out; | ||
524 | } | ||
525 | if (ints & SPI_HOST_INT_SW_UPDATE) { | ||
526 | p54spi_int_ack(priv, SPI_HOST_INT_SW_UPDATE); | ||
527 | ret = p54spi_rx(priv); | ||
528 | if (ret < 0) | ||
529 | goto out; | ||
530 | } | ||
531 | |||
532 | ret = p54spi_wq_tx(priv); | ||
533 | if (ret < 0) | ||
534 | goto out; | ||
535 | |||
536 | ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); | ||
537 | |||
538 | out: | ||
539 | mutex_unlock(&priv->mutex); | ||
540 | } | ||
541 | |||
542 | static int p54spi_op_start(struct ieee80211_hw *dev) | ||
543 | { | ||
544 | struct p54s_priv *priv = dev->priv; | ||
545 | unsigned long timeout; | ||
546 | int ret = 0; | ||
547 | |||
548 | if (mutex_lock_interruptible(&priv->mutex)) { | ||
549 | ret = -EINTR; | ||
550 | goto out; | ||
551 | } | ||
552 | |||
553 | priv->fw_state = FW_STATE_BOOTING; | ||
554 | |||
555 | p54spi_power_on(priv); | ||
556 | |||
557 | ret = p54spi_upload_firmware(dev); | ||
558 | if (ret < 0) { | ||
559 | p54spi_power_off(priv); | ||
560 | goto out_unlock; | ||
561 | } | ||
562 | |||
563 | mutex_unlock(&priv->mutex); | ||
564 | |||
565 | timeout = msecs_to_jiffies(2000); | ||
566 | timeout = wait_for_completion_interruptible_timeout(&priv->fw_comp, | ||
567 | timeout); | ||
568 | if (!timeout) { | ||
569 | dev_err(&priv->spi->dev, "firmware boot failed"); | ||
570 | p54spi_power_off(priv); | ||
571 | ret = -1; | ||
572 | goto out; | ||
573 | } | ||
574 | |||
575 | if (mutex_lock_interruptible(&priv->mutex)) { | ||
576 | ret = -EINTR; | ||
577 | p54spi_power_off(priv); | ||
578 | goto out; | ||
579 | } | ||
580 | |||
581 | WARN_ON(priv->fw_state != FW_STATE_READY); | ||
582 | |||
583 | out_unlock: | ||
584 | mutex_unlock(&priv->mutex); | ||
585 | |||
586 | out: | ||
587 | return ret; | ||
588 | } | ||
589 | |||
590 | static void p54spi_op_stop(struct ieee80211_hw *dev) | ||
591 | { | ||
592 | struct p54s_priv *priv = dev->priv; | ||
593 | |||
594 | if (mutex_lock_interruptible(&priv->mutex)) { | ||
595 | /* FIXME: how to handle this error? */ | ||
596 | return; | ||
597 | } | ||
598 | |||
599 | WARN_ON(priv->fw_state != FW_STATE_READY); | ||
600 | |||
601 | cancel_work_sync(&priv->work); | ||
602 | |||
603 | p54spi_power_off(priv); | ||
604 | spin_lock_bh(&priv->tx_lock); | ||
605 | INIT_LIST_HEAD(&priv->tx_pending); | ||
606 | spin_unlock_bh(&priv->tx_lock); | ||
607 | |||
608 | priv->fw_state = FW_STATE_OFF; | ||
609 | mutex_unlock(&priv->mutex); | ||
610 | } | ||
611 | |||
612 | static int __devinit p54spi_probe(struct spi_device *spi) | ||
613 | { | ||
614 | struct p54s_priv *priv = NULL; | ||
615 | struct ieee80211_hw *hw; | ||
616 | int ret = -EINVAL; | ||
617 | |||
618 | hw = p54_init_common(sizeof(*priv)); | ||
619 | if (!hw) { | ||
620 | dev_err(&priv->spi->dev, "could not alloc ieee80211_hw"); | ||
621 | return -ENOMEM; | ||
622 | } | ||
623 | |||
624 | priv = hw->priv; | ||
625 | priv->hw = hw; | ||
626 | dev_set_drvdata(&spi->dev, priv); | ||
627 | priv->spi = spi; | ||
628 | |||
629 | priv->config = omap_get_config(OMAP_TAG_WLAN_CX3110X, | ||
630 | struct omap_wlan_cx3110x_config); | ||
631 | |||
632 | spi->bits_per_word = 16; | ||
633 | spi->max_speed_hz = 24000000; | ||
634 | |||
635 | ret = spi_setup(spi); | ||
636 | if (ret < 0) { | ||
637 | dev_err(&priv->spi->dev, "spi_setup failed"); | ||
638 | goto err_free_common; | ||
639 | } | ||
640 | |||
641 | ret = gpio_request(priv->config->power_gpio, "p54spi power"); | ||
642 | if (ret < 0) { | ||
643 | dev_err(&priv->spi->dev, "power GPIO request failed: %d", ret); | ||
644 | goto err_free_common; | ||
645 | } | ||
646 | |||
647 | ret = gpio_request(priv->config->irq_gpio, "p54spi irq"); | ||
648 | if (ret < 0) { | ||
649 | dev_err(&priv->spi->dev, "irq GPIO request failed: %d", ret); | ||
650 | goto err_free_common; | ||
651 | } | ||
652 | |||
653 | gpio_direction_output(priv->config->power_gpio, 0); | ||
654 | gpio_direction_input(priv->config->irq_gpio); | ||
655 | |||
656 | ret = request_irq(OMAP_GPIO_IRQ(priv->config->irq_gpio), | ||
657 | p54spi_interrupt, IRQF_DISABLED, "p54spi", | ||
658 | priv->spi); | ||
659 | if (ret < 0) { | ||
660 | dev_err(&priv->spi->dev, "request_irq() failed"); | ||
661 | goto err_free_common; | ||
662 | } | ||
663 | |||
664 | set_irq_type(gpio_to_irq(priv->config->irq_gpio), | ||
665 | IRQ_TYPE_EDGE_RISING); | ||
666 | |||
667 | disable_irq(gpio_to_irq(priv->config->irq_gpio)); | ||
668 | |||
669 | INIT_WORK(&priv->work, p54spi_work); | ||
670 | init_completion(&priv->fw_comp); | ||
671 | INIT_LIST_HEAD(&priv->tx_pending); | ||
672 | mutex_init(&priv->mutex); | ||
673 | SET_IEEE80211_DEV(hw, &spi->dev); | ||
674 | priv->common.open = p54spi_op_start; | ||
675 | priv->common.stop = p54spi_op_stop; | ||
676 | priv->common.tx = p54spi_op_tx; | ||
677 | |||
678 | ret = p54spi_request_firmware(hw); | ||
679 | if (ret < 0) | ||
680 | goto err_free_common; | ||
681 | |||
682 | ret = p54spi_request_eeprom(hw); | ||
683 | if (ret) | ||
684 | goto err_free_common; | ||
685 | |||
686 | ret = ieee80211_register_hw(hw); | ||
687 | if (ret) { | ||
688 | dev_err(&priv->spi->dev, "unable to register " | ||
689 | "mac80211 hw: %d", ret); | ||
690 | goto err_free_common; | ||
691 | } | ||
692 | |||
693 | dev_info(&priv->spi->dev, "device is bound to %s\n", | ||
694 | wiphy_name(hw->wiphy)); | ||
695 | return 0; | ||
696 | |||
697 | err_free_common: | ||
698 | p54_free_common(priv->hw); | ||
699 | return ret; | ||
700 | } | ||
701 | |||
702 | static int __devexit p54spi_remove(struct spi_device *spi) | ||
703 | { | ||
704 | struct p54s_priv *priv = dev_get_drvdata(&spi->dev); | ||
705 | |||
706 | ieee80211_unregister_hw(priv->hw); | ||
707 | |||
708 | free_irq(gpio_to_irq(priv->config->irq_gpio), spi); | ||
709 | |||
710 | gpio_free(priv->config->power_gpio); | ||
711 | gpio_free(priv->config->irq_gpio); | ||
712 | release_firmware(priv->firmware); | ||
713 | |||
714 | mutex_destroy(&priv->mutex); | ||
715 | |||
716 | p54_free_common(priv->hw); | ||
717 | ieee80211_free_hw(priv->hw); | ||
718 | |||
719 | return 0; | ||
720 | } | ||
721 | |||
722 | |||
723 | static struct spi_driver p54spi_driver = { | ||
724 | .driver = { | ||
725 | /* use cx3110x name because board-n800.c uses that for the | ||
726 | * SPI port */ | ||
727 | .name = "cx3110x", | ||
728 | .bus = &spi_bus_type, | ||
729 | .owner = THIS_MODULE, | ||
730 | }, | ||
731 | |||
732 | .probe = p54spi_probe, | ||
733 | .remove = __devexit_p(p54spi_remove), | ||
734 | }; | ||
735 | |||
736 | static int __init p54spi_init(void) | ||
737 | { | ||
738 | int ret; | ||
739 | |||
740 | ret = spi_register_driver(&p54spi_driver); | ||
741 | if (ret < 0) { | ||
742 | printk(KERN_ERR "failed to register SPI driver: %d", ret); | ||
743 | goto out; | ||
744 | } | ||
745 | |||
746 | out: | ||
747 | return ret; | ||
748 | } | ||
749 | |||
750 | static void __exit p54spi_exit(void) | ||
751 | { | ||
752 | spi_unregister_driver(&p54spi_driver); | ||
753 | } | ||
754 | |||
755 | module_init(p54spi_init); | ||
756 | module_exit(p54spi_exit); | ||
757 | |||
758 | MODULE_LICENSE("GPL"); | ||
759 | MODULE_AUTHOR("Christian Lamparter <chunkeey@web.de>"); | ||
diff --git a/drivers/net/wireless/p54/p54spi.h b/drivers/net/wireless/p54/p54spi.h new file mode 100644 index 000000000000..5013ebc8712e --- /dev/null +++ b/drivers/net/wireless/p54/p54spi.h | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2008 Christian Lamparter <chunkeey@web.de> | ||
3 | * | ||
4 | * This driver is a port from stlc45xx: | ||
5 | * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | */ | ||
21 | |||
22 | #ifndef P54SPI_H | ||
23 | #define P54SPI_H | ||
24 | |||
25 | #include <linux/mutex.h> | ||
26 | #include <linux/list.h> | ||
27 | #include <net/mac80211.h> | ||
28 | #include <mach/board.h> | ||
29 | |||
30 | #include "p54.h" | ||
31 | |||
32 | /* Bit 15 is read/write bit; ON = READ, OFF = WRITE */ | ||
33 | #define SPI_ADRS_READ_BIT_15 0x8000 | ||
34 | |||
35 | #define SPI_ADRS_ARM_INTERRUPTS 0x00 | ||
36 | #define SPI_ADRS_ARM_INT_EN 0x04 | ||
37 | |||
38 | #define SPI_ADRS_HOST_INTERRUPTS 0x08 | ||
39 | #define SPI_ADRS_HOST_INT_EN 0x0c | ||
40 | #define SPI_ADRS_HOST_INT_ACK 0x10 | ||
41 | |||
42 | #define SPI_ADRS_GEN_PURP_1 0x14 | ||
43 | #define SPI_ADRS_GEN_PURP_2 0x18 | ||
44 | |||
45 | #define SPI_ADRS_DEV_CTRL_STAT 0x26 /* high word */ | ||
46 | |||
47 | #define SPI_ADRS_DMA_DATA 0x28 | ||
48 | |||
49 | #define SPI_ADRS_DMA_WRITE_CTRL 0x2c | ||
50 | #define SPI_ADRS_DMA_WRITE_LEN 0x2e | ||
51 | #define SPI_ADRS_DMA_WRITE_BASE 0x30 | ||
52 | |||
53 | #define SPI_ADRS_DMA_READ_CTRL 0x34 | ||
54 | #define SPI_ADRS_DMA_READ_LEN 0x36 | ||
55 | #define SPI_ADRS_DMA_READ_BASE 0x38 | ||
56 | |||
57 | #define SPI_CTRL_STAT_HOST_OVERRIDE 0x8000 | ||
58 | #define SPI_CTRL_STAT_START_HALTED 0x4000 | ||
59 | #define SPI_CTRL_STAT_RAM_BOOT 0x2000 | ||
60 | #define SPI_CTRL_STAT_HOST_RESET 0x1000 | ||
61 | #define SPI_CTRL_STAT_HOST_CPU_EN 0x0800 | ||
62 | |||
63 | #define SPI_DMA_WRITE_CTRL_ENABLE 0x0001 | ||
64 | #define SPI_DMA_READ_CTRL_ENABLE 0x0001 | ||
65 | #define HOST_ALLOWED (1 << 7) | ||
66 | |||
67 | #define SPI_TIMEOUT 100 /* msec */ | ||
68 | |||
69 | #define SPI_MAX_TX_PACKETS 32 | ||
70 | |||
71 | #define SPI_MAX_PACKET_SIZE 32767 | ||
72 | |||
73 | #define SPI_TARGET_INT_WAKEUP 0x00000001 | ||
74 | #define SPI_TARGET_INT_SLEEP 0x00000002 | ||
75 | #define SPI_TARGET_INT_RDDONE 0x00000004 | ||
76 | |||
77 | #define SPI_TARGET_INT_CTS 0x00004000 | ||
78 | #define SPI_TARGET_INT_DR 0x00008000 | ||
79 | |||
80 | #define SPI_HOST_INT_READY 0x00000001 | ||
81 | #define SPI_HOST_INT_WR_READY 0x00000002 | ||
82 | #define SPI_HOST_INT_SW_UPDATE 0x00000004 | ||
83 | #define SPI_HOST_INT_UPDATE 0x10000000 | ||
84 | |||
85 | /* clear to send */ | ||
86 | #define SPI_HOST_INT_CR 0x00004000 | ||
87 | |||
88 | /* data ready */ | ||
89 | #define SPI_HOST_INT_DR 0x00008000 | ||
90 | |||
91 | #define SPI_HOST_INTS_DEFAULT \ | ||
92 | (SPI_HOST_INT_READY | SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE) | ||
93 | |||
94 | #define TARGET_BOOT_SLEEP 50 | ||
95 | |||
96 | struct p54s_dma_regs { | ||
97 | __le16 cmd; | ||
98 | __le16 len; | ||
99 | __le32 addr; | ||
100 | } __attribute__ ((packed)); | ||
101 | |||
102 | struct p54s_tx_info { | ||
103 | struct list_head tx_list; | ||
104 | }; | ||
105 | |||
106 | struct p54s_priv { | ||
107 | /* p54_common has to be the first entry */ | ||
108 | struct p54_common common; | ||
109 | struct ieee80211_hw *hw; | ||
110 | struct spi_device *spi; | ||
111 | const struct omap_wlan_cx3110x_config *config; | ||
112 | |||
113 | struct work_struct work; | ||
114 | |||
115 | struct mutex mutex; | ||
116 | struct completion fw_comp; | ||
117 | |||
118 | spinlock_t tx_lock; | ||
119 | |||
120 | /* protected by tx_lock */ | ||
121 | struct list_head tx_pending; | ||
122 | |||
123 | enum fw_state fw_state; | ||
124 | const struct firmware *firmware; | ||
125 | }; | ||
126 | |||
127 | #endif /* P54SPI_H */ | ||