OpenWrt – Blame information for rev 3
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Initialize Owl Emulation Devices |
||
3 | * |
||
4 | * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com> |
||
5 | * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
||
6 | * |
||
7 | * This program is free software; you can redistribute it and/or modify it |
||
8 | * under the terms of the GNU General Public License version 2 as published |
||
9 | * by the Free Software Foundation. |
||
10 | * |
||
11 | * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) |
||
12 | * need to be able to initialize the PCIe wifi device. Normally, this is done |
||
13 | * during the early stages of booting linux, because the necessary init code |
||
14 | * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup. |
||
15 | * However,this isn't possible for devices which have the init code for the |
||
16 | * Atheros chip stored on NAND. Hence, this module can be used to initialze |
||
17 | * the chip when the user-space is ready to extract the init code. |
||
18 | */ |
||
19 | #include <linux/module.h> |
||
20 | #include <linux/version.h> |
||
21 | #include <linux/completion.h> |
||
22 | #include <linux/etherdevice.h> |
||
23 | #include <linux/firmware.h> |
||
24 | #include <linux/pci.h> |
||
25 | #include <linux/delay.h> |
||
26 | #include <linux/platform_device.h> |
||
27 | #include <linux/ath9k_platform.h> |
||
28 | |||
29 | struct owl_ctx { |
||
30 | struct completion eeprom_load; |
||
31 | }; |
||
32 | |||
33 | #define EEPROM_FILENAME_LEN 100 |
||
34 | |||
35 | #define AR5416_EEPROM_MAGIC 0xa55a |
||
36 | |||
37 | static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, |
||
38 | size_t cal_len) |
||
39 | { |
||
40 | void __iomem *mem; |
||
41 | const void *cal_end = (void *)cal_data + cal_len; |
||
42 | const struct { |
||
3 | office | 43 | __be16 reg; |
44 | __be16 low_val; |
||
45 | __be16 high_val; |
||
1 | office | 46 | } __packed *data; |
47 | u16 cmd; |
||
48 | u32 bar0; |
||
49 | bool swap_needed = false; |
||
50 | |||
51 | if (*cal_data != AR5416_EEPROM_MAGIC) { |
||
52 | if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { |
||
53 | dev_err(&pdev->dev, "invalid calibration data\n"); |
||
54 | return -EINVAL; |
||
55 | } |
||
56 | |||
57 | dev_dbg(&pdev->dev, "calibration data needs swapping\n"); |
||
58 | swap_needed = true; |
||
59 | } |
||
60 | |||
61 | dev_info(&pdev->dev, "fixup device configuration\n"); |
||
62 | |||
63 | mem = pcim_iomap(pdev, 0, 0); |
||
64 | if (!mem) { |
||
65 | dev_err(&pdev->dev, "ioremap error\n"); |
||
66 | return -EINVAL; |
||
67 | } |
||
68 | |||
69 | pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); |
||
70 | pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, |
||
71 | pci_resource_start(pdev, 0)); |
||
72 | pci_read_config_word(pdev, PCI_COMMAND, &cmd); |
||
73 | cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; |
||
74 | pci_write_config_word(pdev, PCI_COMMAND, cmd); |
||
75 | |||
76 | /* set pointer to first reg address */ |
||
77 | for (data = (const void *) (cal_data + 3); |
||
3 | office | 78 | (const void *) data <= cal_end && data->reg != cpu_to_be16(~0); |
1 | office | 79 | data++) { |
80 | u32 val; |
||
81 | u16 reg; |
||
82 | |||
83 | reg = data->reg; |
||
84 | val = data->low_val; |
||
3 | office | 85 | val |= data->high_val << 16; |
1 | office | 86 | |
87 | if (swap_needed) { |
||
88 | reg = swab16(reg); |
||
89 | val = swahb32(val); |
||
90 | } |
||
91 | |||
92 | #ifdef CONFIG_LANTIQ |
||
93 | val = swab32(val); |
||
94 | #endif |
||
95 | |||
96 | __raw_writel(val, mem + reg); |
||
97 | udelay(100); |
||
98 | } |
||
99 | |||
100 | pci_read_config_word(pdev, PCI_COMMAND, &cmd); |
||
101 | cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); |
||
102 | pci_write_config_word(pdev, PCI_COMMAND, cmd); |
||
103 | |||
104 | pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); |
||
105 | pcim_iounmap(pdev, mem); |
||
106 | |||
107 | pci_disable_device(pdev); |
||
108 | |||
109 | return 0; |
||
110 | } |
||
111 | |||
112 | static void owl_fw_cb(const struct firmware *fw, void *context) |
||
113 | { |
||
114 | struct pci_dev *pdev = (struct pci_dev *) context; |
||
115 | struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev); |
||
116 | struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev); |
||
117 | struct pci_bus *bus; |
||
118 | |||
119 | complete(&ctx->eeprom_load); |
||
120 | |||
121 | if (!fw) { |
||
122 | dev_err(&pdev->dev, "no eeprom data received.\n"); |
||
123 | goto release; |
||
124 | } |
||
125 | |||
126 | /* also note that we are doing *u16 operations on the file */ |
||
127 | if (fw->size > sizeof(pdata->eeprom_data) || fw->size < 0x200 || |
||
128 | (fw->size & 1) == 1) { |
||
129 | dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); |
||
130 | goto release; |
||
131 | } |
||
132 | |||
133 | if (pdata) { |
||
134 | memcpy(pdata->eeprom_data, fw->data, fw->size); |
||
135 | |||
136 | /* |
||
137 | * eeprom has been successfully loaded - pass the data to ath9k |
||
138 | * but remove the eeprom_name, so it doesn't try to load it too. |
||
139 | */ |
||
140 | pdata->eeprom_name = NULL; |
||
141 | } |
||
142 | |||
143 | if (ath9k_pci_fixup(pdev, (const u16 *) fw->data, fw->size)) |
||
144 | goto release; |
||
145 | |||
146 | pci_lock_rescan_remove(); |
||
147 | bus = pdev->bus; |
||
148 | pci_stop_and_remove_bus_device(pdev); |
||
149 | /* |
||
150 | * the device should come back with the proper |
||
151 | * ProductId. But we have to initiate a rescan. |
||
152 | */ |
||
153 | pci_rescan_bus(bus); |
||
154 | pci_unlock_rescan_remove(); |
||
155 | |||
156 | release: |
||
157 | release_firmware(fw); |
||
158 | } |
||
159 | |||
160 | static const char *owl_get_eeprom_name(struct pci_dev *pdev) |
||
161 | { |
||
162 | struct device *dev = &pdev->dev; |
||
163 | struct ath9k_platform_data *pdata; |
||
164 | char *eeprom_name; |
||
165 | |||
166 | /* try the existing platform data first */ |
||
167 | pdata = dev_get_platdata(dev); |
||
168 | if (pdata && pdata->eeprom_name) |
||
169 | return pdata->eeprom_name; |
||
170 | |||
171 | dev_dbg(dev, "using auto-generated eeprom filename\n"); |
||
172 | |||
173 | eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); |
||
174 | if (!eeprom_name) |
||
175 | return NULL; |
||
176 | |||
177 | /* this should match the pattern used in ath9k/init.c */ |
||
178 | scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", |
||
179 | dev_name(dev)); |
||
180 | |||
181 | return eeprom_name; |
||
182 | } |
||
183 | |||
184 | static int owl_probe(struct pci_dev *pdev, |
||
185 | const struct pci_device_id *id) |
||
186 | { |
||
187 | struct owl_ctx *ctx; |
||
188 | const char *eeprom_name; |
||
189 | int err = 0; |
||
190 | |||
191 | if (pcim_enable_device(pdev)) |
||
192 | return -EIO; |
||
193 | |||
194 | pcim_pin_device(pdev); |
||
195 | |||
196 | eeprom_name = owl_get_eeprom_name(pdev); |
||
197 | if (!eeprom_name) { |
||
198 | dev_err(&pdev->dev, "no eeprom filename found.\n"); |
||
199 | return -ENODEV; |
||
200 | } |
||
201 | |||
202 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
||
203 | if (!ctx) { |
||
204 | dev_err(&pdev->dev, "failed to alloc device context.\n"); |
||
205 | return -ENOMEM; |
||
206 | } |
||
207 | init_completion(&ctx->eeprom_load); |
||
208 | |||
209 | pci_set_drvdata(pdev, ctx); |
||
210 | err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, |
||
211 | &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); |
||
212 | if (err) { |
||
213 | dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); |
||
214 | kfree(ctx); |
||
215 | } |
||
216 | return err; |
||
217 | } |
||
218 | |||
219 | static void owl_remove(struct pci_dev *pdev) |
||
220 | { |
||
221 | struct owl_ctx *ctx = pci_get_drvdata(pdev); |
||
222 | |||
223 | if (ctx) { |
||
224 | wait_for_completion(&ctx->eeprom_load); |
||
225 | pci_set_drvdata(pdev, NULL); |
||
226 | kfree(ctx); |
||
227 | } |
||
228 | } |
||
229 | |||
230 | static const struct pci_device_id owl_pci_table[] = { |
||
231 | { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ |
||
232 | { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ |
||
233 | { }, |
||
234 | }; |
||
235 | MODULE_DEVICE_TABLE(pci, owl_pci_table); |
||
236 | |||
237 | static struct pci_driver owl_driver = { |
||
238 | .name = "owl-loader", |
||
239 | .id_table = owl_pci_table, |
||
240 | .probe = owl_probe, |
||
241 | .remove = owl_remove, |
||
242 | }; |
||
243 | module_pci_driver(owl_driver); |
||
244 | MODULE_AUTHOR("Christian Lamparter <chunkeey@googlemail.com>"); |
||
245 | MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices"); |
||
246 | MODULE_LICENSE("GPL v2"); |