1 | // SPDX-License-Identifier: ISC |
2 | /* Initialize Owl Emulation Devices |
3 | * |
4 | * Copyright (C) 2016 Christian Lamparter <chunkeey@gmail.com> |
5 | * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
6 | * |
7 | * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) |
8 | * need to be able to initialize the PCIe wifi device. Normally, this is done |
9 | * during the early stages as a pci quirk. |
10 | * However, this isn't possible for devices which have the init code for the |
11 | * Atheros chip stored on UBI Volume on NAND. Hence, this module can be used to |
12 | * initialize the chip when the user-space is ready to extract the init code. |
13 | */ |
14 | #include <linux/module.h> |
15 | #include <linux/completion.h> |
16 | #include <linux/etherdevice.h> |
17 | #include <linux/firmware.h> |
18 | #include <linux/pci.h> |
19 | #include <linux/delay.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/ath9k_platform.h> |
22 | #include <linux/nvmem-consumer.h> |
23 | #include <linux/workqueue.h> |
24 | |
25 | struct owl_ctx { |
26 | struct pci_dev *pdev; |
27 | struct completion eeprom_load; |
28 | struct work_struct work; |
29 | struct nvmem_cell *cell; |
30 | }; |
31 | |
32 | #define EEPROM_FILENAME_LEN 100 |
33 | |
34 | #define AR5416_EEPROM_MAGIC 0xa55a |
35 | |
36 | static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, |
37 | size_t cal_len) |
38 | { |
39 | void __iomem *mem; |
40 | const void *cal_end = (void *)cal_data + cal_len; |
41 | const struct { |
42 | u16 reg; |
43 | u16 low_val; |
44 | u16 high_val; |
45 | } __packed * data; |
46 | u16 cmd; |
47 | u32 bar0; |
48 | bool swap_needed = false; |
49 | |
50 | /* also note that we are doing *u16 operations on the file */ |
51 | if (cal_len > 4096 || cal_len < 0x200 || (cal_len & 1) == 1) { |
52 | dev_err(&pdev->dev, "eeprom has an invalid size.\n" ); |
53 | return -EINVAL; |
54 | } |
55 | |
56 | if (*cal_data != AR5416_EEPROM_MAGIC) { |
57 | if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { |
58 | dev_err(&pdev->dev, "invalid calibration data\n" ); |
59 | return -EINVAL; |
60 | } |
61 | |
62 | dev_dbg(&pdev->dev, "calibration data needs swapping\n" ); |
63 | swap_needed = true; |
64 | } |
65 | |
66 | dev_info(&pdev->dev, "fixup device configuration\n" ); |
67 | |
68 | mem = pcim_iomap(pdev, bar: 0, maxlen: 0); |
69 | if (!mem) { |
70 | dev_err(&pdev->dev, "ioremap error\n" ); |
71 | return -EINVAL; |
72 | } |
73 | |
74 | pci_read_config_dword(dev: pdev, PCI_BASE_ADDRESS_0, val: &bar0); |
75 | pci_write_config_dword(dev: pdev, PCI_BASE_ADDRESS_0, |
76 | pci_resource_start(pdev, 0)); |
77 | pci_read_config_word(dev: pdev, PCI_COMMAND, val: &cmd); |
78 | cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; |
79 | pci_write_config_word(dev: pdev, PCI_COMMAND, val: cmd); |
80 | |
81 | /* set pointer to first reg address */ |
82 | for (data = (const void *)(cal_data + 3); |
83 | (const void *)data <= cal_end && data->reg != (u16)~0; |
84 | data++) { |
85 | u32 val; |
86 | u16 reg; |
87 | |
88 | reg = data->reg; |
89 | val = data->low_val; |
90 | val |= ((u32)data->high_val) << 16; |
91 | |
92 | if (swap_needed) { |
93 | reg = swab16(reg); |
94 | val = swahb32(val); |
95 | } |
96 | |
97 | iowrite32(val, mem + reg); |
98 | usleep_range(min: 100, max: 120); |
99 | } |
100 | |
101 | pci_read_config_word(dev: pdev, PCI_COMMAND, val: &cmd); |
102 | cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); |
103 | pci_write_config_word(dev: pdev, PCI_COMMAND, val: cmd); |
104 | |
105 | pci_write_config_dword(dev: pdev, PCI_BASE_ADDRESS_0, val: bar0); |
106 | pcim_iounmap(pdev, addr: mem); |
107 | |
108 | pci_disable_device(dev: pdev); |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | static void owl_rescan(struct pci_dev *pdev) |
114 | { |
115 | struct pci_bus *bus = pdev->bus; |
116 | |
117 | pci_lock_rescan_remove(); |
118 | pci_stop_and_remove_bus_device(dev: pdev); |
119 | /* the device should come back with the proper |
120 | * ProductId. But we have to initiate a rescan. |
121 | */ |
122 | pci_rescan_bus(bus); |
123 | pci_unlock_rescan_remove(); |
124 | } |
125 | |
126 | static void owl_fw_cb(const struct firmware *fw, void *context) |
127 | { |
128 | struct owl_ctx *ctx = context; |
129 | |
130 | complete(&ctx->eeprom_load); |
131 | |
132 | if (fw) { |
133 | ath9k_pci_fixup(pdev: ctx->pdev, cal_data: (const u16 *)fw->data, cal_len: fw->size); |
134 | owl_rescan(pdev: ctx->pdev); |
135 | } else { |
136 | dev_err(&ctx->pdev->dev, "no eeprom data received.\n" ); |
137 | } |
138 | release_firmware(fw); |
139 | } |
140 | |
141 | static const char *owl_get_eeprom_name(struct pci_dev *pdev) |
142 | { |
143 | struct device *dev = &pdev->dev; |
144 | char *eeprom_name; |
145 | |
146 | dev_dbg(dev, "using auto-generated eeprom filename\n" ); |
147 | |
148 | eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); |
149 | if (!eeprom_name) |
150 | return NULL; |
151 | |
152 | /* this should match the pattern used in ath9k/init.c */ |
153 | scnprintf(buf: eeprom_name, EEPROM_FILENAME_LEN, fmt: "ath9k-eeprom-pci-%s.bin" , |
154 | dev_name(dev)); |
155 | |
156 | return eeprom_name; |
157 | } |
158 | |
159 | static void owl_nvmem_work(struct work_struct *work) |
160 | { |
161 | struct owl_ctx *ctx = container_of(work, struct owl_ctx, work); |
162 | void *buf; |
163 | size_t len; |
164 | |
165 | complete(&ctx->eeprom_load); |
166 | |
167 | buf = nvmem_cell_read(cell: ctx->cell, len: &len); |
168 | if (!IS_ERR(ptr: buf)) { |
169 | ath9k_pci_fixup(pdev: ctx->pdev, cal_data: buf, cal_len: len); |
170 | kfree(objp: buf); |
171 | owl_rescan(pdev: ctx->pdev); |
172 | } else { |
173 | dev_err(&ctx->pdev->dev, "no nvmem data received.\n" ); |
174 | } |
175 | } |
176 | |
177 | static int owl_nvmem_probe(struct owl_ctx *ctx) |
178 | { |
179 | int err; |
180 | |
181 | ctx->cell = devm_nvmem_cell_get(dev: &ctx->pdev->dev, id: "calibration" ); |
182 | if (IS_ERR(ptr: ctx->cell)) { |
183 | err = PTR_ERR(ptr: ctx->cell); |
184 | if (err == -ENOENT || err == -EOPNOTSUPP) |
185 | return 1; /* not present, try firmware_request */ |
186 | |
187 | return err; |
188 | } |
189 | |
190 | INIT_WORK(&ctx->work, owl_nvmem_work); |
191 | schedule_work(work: &ctx->work); |
192 | |
193 | return 0; |
194 | } |
195 | |
196 | static int owl_probe(struct pci_dev *pdev, |
197 | const struct pci_device_id *id) |
198 | { |
199 | struct owl_ctx *ctx; |
200 | const char *eeprom_name; |
201 | int err = 0; |
202 | |
203 | if (pcim_enable_device(pdev)) |
204 | return -EIO; |
205 | |
206 | pcim_pin_device(pdev); |
207 | |
208 | ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ctx), GFP_KERNEL); |
209 | if (!ctx) |
210 | return -ENOMEM; |
211 | |
212 | init_completion(x: &ctx->eeprom_load); |
213 | ctx->pdev = pdev; |
214 | |
215 | pci_set_drvdata(pdev, data: ctx); |
216 | |
217 | err = owl_nvmem_probe(ctx); |
218 | if (err <= 0) |
219 | return err; |
220 | |
221 | eeprom_name = owl_get_eeprom_name(pdev); |
222 | if (!eeprom_name) { |
223 | dev_err(&pdev->dev, "no eeprom filename found.\n" ); |
224 | return -ENODEV; |
225 | } |
226 | |
227 | err = request_firmware_nowait(THIS_MODULE, uevent: true, name: eeprom_name, |
228 | device: &pdev->dev, GFP_KERNEL, context: ctx, cont: owl_fw_cb); |
229 | if (err) |
230 | dev_err(&pdev->dev, "failed to request caldata (%d).\n" , err); |
231 | |
232 | return err; |
233 | } |
234 | |
235 | static void owl_remove(struct pci_dev *pdev) |
236 | { |
237 | struct owl_ctx *ctx = pci_get_drvdata(pdev); |
238 | |
239 | if (ctx) { |
240 | wait_for_completion(&ctx->eeprom_load); |
241 | pci_set_drvdata(pdev, NULL); |
242 | } |
243 | } |
244 | |
245 | static const struct pci_device_id owl_pci_table[] = { |
246 | { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ |
247 | { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ |
248 | { }, |
249 | }; |
250 | MODULE_DEVICE_TABLE(pci, owl_pci_table); |
251 | |
252 | static struct pci_driver owl_driver = { |
253 | .name = KBUILD_MODNAME, |
254 | .id_table = owl_pci_table, |
255 | .probe = owl_probe, |
256 | .remove = owl_remove, |
257 | }; |
258 | module_pci_driver(owl_driver); |
259 | MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>" ); |
260 | MODULE_DESCRIPTION("External EEPROM data loader for Atheros AR500X to AR92XX" ); |
261 | MODULE_LICENSE("Dual BSD/GPL" ); |
262 | |