]>
Commit | Line | Data |
---|---|---|
5a4f2040 CL |
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> | |
5a4f2040 CL |
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 | ||
23 | struct owl_ctx { | |
24 | struct completion eeprom_load; | |
25 | }; | |
26 | ||
27 | #define EEPROM_FILENAME_LEN 100 | |
28 | ||
29 | #define AR5416_EEPROM_MAGIC 0xa55a | |
30 | ||
31 | static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, | |
32 | size_t cal_len) | |
33 | { | |
34 | void __iomem *mem; | |
35 | const void *cal_end = (void *)cal_data + cal_len; | |
36 | const struct { | |
37 | u16 reg; | |
38 | u16 low_val; | |
39 | u16 high_val; | |
40 | } __packed * data; | |
41 | u16 cmd; | |
42 | u32 bar0; | |
43 | bool swap_needed = false; | |
44 | ||
45 | if (*cal_data != AR5416_EEPROM_MAGIC) { | |
46 | if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { | |
47 | dev_err(&pdev->dev, "invalid calibration data\n"); | |
48 | return -EINVAL; | |
49 | } | |
50 | ||
51 | dev_dbg(&pdev->dev, "calibration data needs swapping\n"); | |
52 | swap_needed = true; | |
53 | } | |
54 | ||
55 | dev_info(&pdev->dev, "fixup device configuration\n"); | |
56 | ||
57 | mem = pcim_iomap(pdev, 0, 0); | |
58 | if (!mem) { | |
59 | dev_err(&pdev->dev, "ioremap error\n"); | |
60 | return -EINVAL; | |
61 | } | |
62 | ||
63 | pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); | |
64 | pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, | |
65 | pci_resource_start(pdev, 0)); | |
66 | pci_read_config_word(pdev, PCI_COMMAND, &cmd); | |
67 | cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; | |
68 | pci_write_config_word(pdev, PCI_COMMAND, cmd); | |
69 | ||
70 | /* set pointer to first reg address */ | |
71 | for (data = (const void *)(cal_data + 3); | |
72 | (const void *)data <= cal_end && data->reg != (u16)~0; | |
73 | data++) { | |
74 | u32 val; | |
75 | u16 reg; | |
76 | ||
77 | reg = data->reg; | |
78 | val = data->low_val; | |
79 | val |= ((u32)data->high_val) << 16; | |
80 | ||
81 | if (swap_needed) { | |
82 | reg = swab16(reg); | |
83 | val = swahb32(val); | |
84 | } | |
85 | ||
86 | __raw_writel(val, mem + reg); | |
87 | usleep_range(100, 120); | |
88 | } | |
89 | ||
90 | pci_read_config_word(pdev, PCI_COMMAND, &cmd); | |
91 | cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); | |
92 | pci_write_config_word(pdev, PCI_COMMAND, cmd); | |
93 | ||
94 | pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); | |
95 | pcim_iounmap(pdev, mem); | |
96 | ||
97 | pci_disable_device(pdev); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static void owl_fw_cb(const struct firmware *fw, void *context) | |
103 | { | |
104 | struct pci_dev *pdev = (struct pci_dev *)context; | |
105 | struct owl_ctx *ctx = (struct owl_ctx *)pci_get_drvdata(pdev); | |
106 | struct pci_bus *bus; | |
107 | ||
108 | complete(&ctx->eeprom_load); | |
109 | ||
110 | if (!fw) { | |
111 | dev_err(&pdev->dev, "no eeprom data received.\n"); | |
112 | goto release; | |
113 | } | |
114 | ||
115 | /* also note that we are doing *u16 operations on the file */ | |
116 | if (fw->size > 4096 || fw->size < 0x200 || (fw->size & 1) == 1) { | |
117 | dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); | |
118 | goto release; | |
119 | } | |
120 | ||
121 | if (ath9k_pci_fixup(pdev, (const u16 *)fw->data, fw->size)) | |
122 | goto release; | |
123 | ||
124 | pci_lock_rescan_remove(); | |
125 | bus = pdev->bus; | |
126 | pci_stop_and_remove_bus_device(pdev); | |
127 | /* the device should come back with the proper | |
128 | * ProductId. But we have to initiate a rescan. | |
129 | */ | |
130 | pci_rescan_bus(bus); | |
131 | pci_unlock_rescan_remove(); | |
132 | ||
133 | release: | |
134 | release_firmware(fw); | |
135 | } | |
136 | ||
137 | static const char *owl_get_eeprom_name(struct pci_dev *pdev) | |
138 | { | |
139 | struct device *dev = &pdev->dev; | |
140 | char *eeprom_name; | |
141 | ||
142 | dev_dbg(dev, "using auto-generated eeprom filename\n"); | |
143 | ||
144 | eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); | |
145 | if (!eeprom_name) | |
146 | return NULL; | |
147 | ||
148 | /* this should match the pattern used in ath9k/init.c */ | |
149 | scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", | |
150 | dev_name(dev)); | |
151 | ||
152 | return eeprom_name; | |
153 | } | |
154 | ||
155 | static int owl_probe(struct pci_dev *pdev, | |
156 | const struct pci_device_id *id) | |
157 | { | |
158 | struct owl_ctx *ctx; | |
159 | const char *eeprom_name; | |
160 | int err = 0; | |
161 | ||
162 | if (pcim_enable_device(pdev)) | |
163 | return -EIO; | |
164 | ||
165 | pcim_pin_device(pdev); | |
166 | ||
167 | eeprom_name = owl_get_eeprom_name(pdev); | |
168 | if (!eeprom_name) { | |
169 | dev_err(&pdev->dev, "no eeprom filename found.\n"); | |
170 | return -ENODEV; | |
171 | } | |
172 | ||
173 | ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); | |
174 | if (!ctx) | |
175 | return -ENOMEM; | |
176 | ||
177 | init_completion(&ctx->eeprom_load); | |
178 | ||
179 | pci_set_drvdata(pdev, ctx); | |
180 | err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, | |
181 | &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); | |
182 | if (err) | |
183 | dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); | |
184 | ||
185 | return err; | |
186 | } | |
187 | ||
188 | static void owl_remove(struct pci_dev *pdev) | |
189 | { | |
190 | struct owl_ctx *ctx = pci_get_drvdata(pdev); | |
191 | ||
192 | if (ctx) { | |
193 | wait_for_completion(&ctx->eeprom_load); | |
194 | pci_set_drvdata(pdev, NULL); | |
195 | } | |
196 | } | |
197 | ||
198 | static const struct pci_device_id owl_pci_table[] = { | |
199 | { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ | |
200 | { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ | |
201 | { }, | |
202 | }; | |
203 | MODULE_DEVICE_TABLE(pci, owl_pci_table); | |
204 | ||
205 | static struct pci_driver owl_driver = { | |
206 | .name = KBUILD_MODNAME, | |
207 | .id_table = owl_pci_table, | |
208 | .probe = owl_probe, | |
209 | .remove = owl_remove, | |
210 | }; | |
211 | module_pci_driver(owl_driver); | |
212 | MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>"); | |
213 | MODULE_DESCRIPTION("External EEPROM data loader for Atheros AR500X to AR92XX"); | |
214 | MODULE_LICENSE("Dual BSD/GPL"); |