Commit | Line | Data |
---|---|---|
6c223761 KB |
1 | /* |
2 | * driver for Microsemi PQI-based storage controllers | |
3 | * Copyright (c) 2016 Microsemi Corporation | |
4 | * Copyright (c) 2016 PMC-Sierra, Inc. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; version 2 of the License. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
13 | * NON INFRINGEMENT. See the GNU General Public License for more details. | |
14 | * | |
15 | * Questions/Comments/Bugfixes to esc.storagedev@microsemi.com | |
16 | * | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/delay.h> | |
22 | #include <linux/pci.h> | |
23 | #include <scsi/scsi_device.h> | |
24 | #include <asm/unaligned.h> | |
25 | #include "smartpqi.h" | |
26 | #include "smartpqi_sis.h" | |
27 | ||
28 | /* legacy SIS interface commands */ | |
29 | #define SIS_CMD_GET_ADAPTER_PROPERTIES 0x19 | |
30 | #define SIS_CMD_INIT_BASE_STRUCT_ADDRESS 0x1b | |
31 | #define SIS_CMD_GET_PQI_CAPABILITIES 0x3000 | |
32 | ||
33 | /* for submission of legacy SIS commands */ | |
34 | #define SIS_REENABLE_SIS_MODE 0x1 | |
35 | #define SIS_ENABLE_MSIX 0x40 | |
36 | #define SIS_SOFT_RESET 0x100 | |
37 | #define SIS_CMD_READY 0x200 | |
38 | #define SIS_CMD_COMPLETE 0x1000 | |
39 | #define SIS_CLEAR_CTRL_TO_HOST_DOORBELL 0x1000 | |
40 | #define SIS_CMD_STATUS_SUCCESS 0x1 | |
41 | #define SIS_CMD_COMPLETE_TIMEOUT_SECS 30 | |
42 | #define SIS_CMD_COMPLETE_POLL_INTERVAL_MSECS 10 | |
43 | ||
44 | /* used with SIS_CMD_GET_ADAPTER_PROPERTIES command */ | |
45 | #define SIS_EXTENDED_PROPERTIES_SUPPORTED 0x800000 | |
46 | #define SIS_SMARTARRAY_FEATURES_SUPPORTED 0x2 | |
47 | #define SIS_PQI_MODE_SUPPORTED 0x4 | |
48 | #define SIS_REQUIRED_EXTENDED_PROPERTIES \ | |
49 | (SIS_SMARTARRAY_FEATURES_SUPPORTED | SIS_PQI_MODE_SUPPORTED) | |
50 | ||
51 | /* used with SIS_CMD_INIT_BASE_STRUCT_ADDRESS command */ | |
52 | #define SIS_BASE_STRUCT_REVISION 9 | |
53 | #define SIS_BASE_STRUCT_ALIGNMENT 16 | |
54 | ||
55 | #define SIS_CTRL_KERNEL_UP 0x80 | |
56 | #define SIS_CTRL_KERNEL_PANIC 0x100 | |
57 | #define SIS_CTRL_READY_TIMEOUT_SECS 30 | |
58 | #define SIS_CTRL_READY_POLL_INTERVAL_MSECS 10 | |
59 | ||
60 | #pragma pack(1) | |
61 | ||
62 | /* for use with SIS_CMD_INIT_BASE_STRUCT_ADDRESS command */ | |
63 | struct sis_base_struct { | |
64 | __le32 revision; /* revision of this structure */ | |
65 | __le32 flags; /* reserved */ | |
66 | __le32 error_buffer_paddr_low; /* lower 32 bits of physical memory */ | |
67 | /* buffer for PQI error response */ | |
68 | /* data */ | |
69 | __le32 error_buffer_paddr_high; /* upper 32 bits of physical */ | |
70 | /* memory buffer for PQI */ | |
71 | /* error response data */ | |
72 | __le32 error_buffer_element_length; /* length of each PQI error */ | |
73 | /* response buffer element */ | |
74 | /* in bytes */ | |
75 | __le32 error_buffer_num_elements; /* total number of PQI error */ | |
76 | /* response buffers available */ | |
77 | }; | |
78 | ||
79 | #pragma pack() | |
80 | ||
81 | int sis_wait_for_ctrl_ready(struct pqi_ctrl_info *ctrl_info) | |
82 | { | |
83 | unsigned long timeout; | |
84 | u32 status; | |
85 | ||
86 | timeout = (SIS_CTRL_READY_TIMEOUT_SECS * HZ) + jiffies; | |
87 | ||
88 | while (1) { | |
89 | status = readl(&ctrl_info->registers->sis_firmware_status); | |
90 | if (status != ~0) { | |
91 | if (status & SIS_CTRL_KERNEL_PANIC) { | |
92 | dev_err(&ctrl_info->pci_dev->dev, | |
93 | "controller is offline: status code 0x%x\n", | |
94 | readl( | |
95 | &ctrl_info->registers->sis_mailbox[7])); | |
96 | return -ENODEV; | |
97 | } | |
98 | if (status & SIS_CTRL_KERNEL_UP) | |
99 | break; | |
100 | } | |
101 | if (time_after(jiffies, timeout)) | |
102 | return -ETIMEDOUT; | |
103 | msleep(SIS_CTRL_READY_POLL_INTERVAL_MSECS); | |
104 | } | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
109 | bool sis_is_firmware_running(struct pqi_ctrl_info *ctrl_info) | |
110 | { | |
111 | bool running; | |
112 | u32 status; | |
113 | ||
114 | status = readl(&ctrl_info->registers->sis_firmware_status); | |
115 | ||
116 | if (status & SIS_CTRL_KERNEL_PANIC) | |
117 | running = false; | |
118 | else | |
119 | running = true; | |
120 | ||
121 | if (!running) | |
122 | dev_err(&ctrl_info->pci_dev->dev, | |
123 | "controller is offline: status code 0x%x\n", | |
124 | readl(&ctrl_info->registers->sis_mailbox[7])); | |
125 | ||
126 | return running; | |
127 | } | |
128 | ||
129 | /* used for passing command parameters/results when issuing SIS commands */ | |
130 | struct sis_sync_cmd_params { | |
131 | u32 mailbox[6]; /* mailboxes 0-5 */ | |
132 | }; | |
133 | ||
134 | static int sis_send_sync_cmd(struct pqi_ctrl_info *ctrl_info, | |
135 | u32 cmd, struct sis_sync_cmd_params *params) | |
136 | { | |
137 | struct pqi_ctrl_registers __iomem *registers; | |
138 | unsigned int i; | |
139 | unsigned long timeout; | |
140 | u32 doorbell; | |
141 | u32 cmd_status; | |
142 | ||
143 | registers = ctrl_info->registers; | |
144 | ||
145 | /* Write the command to mailbox 0. */ | |
146 | writel(cmd, ®isters->sis_mailbox[0]); | |
147 | ||
148 | /* | |
149 | * Write the command parameters to mailboxes 1-4 (mailbox 5 is not used | |
150 | * when sending a command to the controller). | |
151 | */ | |
152 | for (i = 1; i <= 4; i++) | |
153 | writel(params->mailbox[i], ®isters->sis_mailbox[i]); | |
154 | ||
155 | /* Clear the command doorbell. */ | |
156 | writel(SIS_CLEAR_CTRL_TO_HOST_DOORBELL, | |
157 | ®isters->sis_ctrl_to_host_doorbell_clear); | |
158 | ||
159 | /* Disable doorbell interrupts by masking all interrupts. */ | |
160 | writel(~0, ®isters->sis_interrupt_mask); | |
161 | ||
162 | /* | |
163 | * Force the completion of the interrupt mask register write before | |
164 | * submitting the command. | |
165 | */ | |
166 | readl(®isters->sis_interrupt_mask); | |
167 | ||
168 | /* Submit the command to the controller. */ | |
169 | writel(SIS_CMD_READY, ®isters->sis_host_to_ctrl_doorbell); | |
170 | ||
171 | /* | |
172 | * Poll for command completion. Note that the call to msleep() is at | |
173 | * the top of the loop in order to give the controller time to start | |
174 | * processing the command before we start polling. | |
175 | */ | |
176 | timeout = (SIS_CMD_COMPLETE_TIMEOUT_SECS * HZ) + jiffies; | |
177 | while (1) { | |
178 | msleep(SIS_CMD_COMPLETE_POLL_INTERVAL_MSECS); | |
179 | doorbell = readl(®isters->sis_ctrl_to_host_doorbell); | |
180 | if (doorbell & SIS_CMD_COMPLETE) | |
181 | break; | |
182 | if (time_after(jiffies, timeout)) | |
183 | return -ETIMEDOUT; | |
184 | } | |
185 | ||
186 | /* Read the command status from mailbox 0. */ | |
187 | cmd_status = readl(®isters->sis_mailbox[0]); | |
188 | if (cmd_status != SIS_CMD_STATUS_SUCCESS) { | |
189 | dev_err(&ctrl_info->pci_dev->dev, | |
190 | "SIS command failed for command 0x%x: status = 0x%x\n", | |
191 | cmd, cmd_status); | |
192 | return -EINVAL; | |
193 | } | |
194 | ||
195 | /* | |
196 | * The command completed successfully, so save the command status and | |
197 | * read the values returned in mailboxes 1-5. | |
198 | */ | |
199 | params->mailbox[0] = cmd_status; | |
200 | for (i = 1; i < ARRAY_SIZE(params->mailbox); i++) | |
201 | params->mailbox[i] = readl(®isters->sis_mailbox[i]); | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | /* | |
207 | * This function verifies that we are talking to a controller that speaks PQI. | |
208 | */ | |
209 | ||
210 | int sis_get_ctrl_properties(struct pqi_ctrl_info *ctrl_info) | |
211 | { | |
212 | int rc; | |
213 | u32 properties; | |
214 | u32 extended_properties; | |
215 | struct sis_sync_cmd_params params; | |
216 | ||
217 | memset(¶ms, 0, sizeof(params)); | |
218 | ||
219 | rc = sis_send_sync_cmd(ctrl_info, SIS_CMD_GET_ADAPTER_PROPERTIES, | |
220 | ¶ms); | |
221 | if (rc) | |
222 | return rc; | |
223 | ||
224 | properties = params.mailbox[1]; | |
225 | ||
226 | if (!(properties & SIS_EXTENDED_PROPERTIES_SUPPORTED)) | |
227 | return -ENODEV; | |
228 | ||
229 | extended_properties = params.mailbox[4]; | |
230 | ||
231 | if ((extended_properties & SIS_REQUIRED_EXTENDED_PROPERTIES) != | |
232 | SIS_REQUIRED_EXTENDED_PROPERTIES) | |
233 | return -ENODEV; | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
238 | int sis_get_pqi_capabilities(struct pqi_ctrl_info *ctrl_info) | |
239 | { | |
240 | int rc; | |
241 | struct sis_sync_cmd_params params; | |
242 | ||
243 | memset(¶ms, 0, sizeof(params)); | |
244 | ||
245 | rc = sis_send_sync_cmd(ctrl_info, SIS_CMD_GET_PQI_CAPABILITIES, | |
246 | ¶ms); | |
247 | if (rc) | |
248 | return rc; | |
249 | ||
250 | ctrl_info->max_sg_entries = params.mailbox[1]; | |
251 | ctrl_info->max_transfer_size = params.mailbox[2]; | |
252 | ctrl_info->max_outstanding_requests = params.mailbox[3]; | |
253 | ctrl_info->config_table_offset = params.mailbox[4]; | |
254 | ctrl_info->config_table_length = params.mailbox[5]; | |
255 | ||
256 | return 0; | |
257 | } | |
258 | ||
259 | int sis_init_base_struct_addr(struct pqi_ctrl_info *ctrl_info) | |
260 | { | |
261 | int rc; | |
262 | void *base_struct_unaligned; | |
263 | struct sis_base_struct *base_struct; | |
264 | struct sis_sync_cmd_params params; | |
265 | unsigned long error_buffer_paddr; | |
266 | dma_addr_t bus_address; | |
267 | ||
268 | base_struct_unaligned = kzalloc(sizeof(*base_struct) | |
269 | + SIS_BASE_STRUCT_ALIGNMENT - 1, GFP_KERNEL); | |
270 | if (!base_struct_unaligned) | |
271 | return -ENOMEM; | |
272 | ||
273 | base_struct = PTR_ALIGN(base_struct_unaligned, | |
274 | SIS_BASE_STRUCT_ALIGNMENT); | |
275 | error_buffer_paddr = (unsigned long)ctrl_info->error_buffer_dma_handle; | |
276 | ||
277 | put_unaligned_le32(SIS_BASE_STRUCT_REVISION, &base_struct->revision); | |
278 | put_unaligned_le32(lower_32_bits(error_buffer_paddr), | |
279 | &base_struct->error_buffer_paddr_low); | |
280 | put_unaligned_le32(upper_32_bits(error_buffer_paddr), | |
281 | &base_struct->error_buffer_paddr_high); | |
282 | put_unaligned_le32(PQI_ERROR_BUFFER_ELEMENT_LENGTH, | |
283 | &base_struct->error_buffer_element_length); | |
284 | put_unaligned_le32(ctrl_info->max_io_slots, | |
285 | &base_struct->error_buffer_num_elements); | |
286 | ||
287 | bus_address = pci_map_single(ctrl_info->pci_dev, base_struct, | |
288 | sizeof(*base_struct), PCI_DMA_TODEVICE); | |
289 | if (pci_dma_mapping_error(ctrl_info->pci_dev, bus_address)) { | |
290 | rc = -ENOMEM; | |
291 | goto out; | |
292 | } | |
293 | ||
294 | memset(¶ms, 0, sizeof(params)); | |
295 | params.mailbox[1] = lower_32_bits((u64)bus_address); | |
296 | params.mailbox[2] = upper_32_bits((u64)bus_address); | |
297 | params.mailbox[3] = sizeof(*base_struct); | |
298 | ||
299 | rc = sis_send_sync_cmd(ctrl_info, SIS_CMD_INIT_BASE_STRUCT_ADDRESS, | |
300 | ¶ms); | |
301 | ||
302 | pci_unmap_single(ctrl_info->pci_dev, bus_address, sizeof(*base_struct), | |
303 | PCI_DMA_TODEVICE); | |
304 | ||
305 | out: | |
306 | kfree(base_struct_unaligned); | |
307 | ||
308 | return rc; | |
309 | } | |
310 | ||
311 | /* Enable MSI-X interrupts on the controller. */ | |
312 | ||
313 | void sis_enable_msix(struct pqi_ctrl_info *ctrl_info) | |
314 | { | |
315 | u32 doorbell_register; | |
316 | ||
317 | doorbell_register = | |
318 | readl(&ctrl_info->registers->sis_host_to_ctrl_doorbell); | |
319 | doorbell_register |= SIS_ENABLE_MSIX; | |
320 | ||
321 | writel(doorbell_register, | |
322 | &ctrl_info->registers->sis_host_to_ctrl_doorbell); | |
323 | } | |
324 | ||
325 | /* Disable MSI-X interrupts on the controller. */ | |
326 | ||
327 | void sis_disable_msix(struct pqi_ctrl_info *ctrl_info) | |
328 | { | |
329 | u32 doorbell_register; | |
330 | ||
331 | doorbell_register = | |
332 | readl(&ctrl_info->registers->sis_host_to_ctrl_doorbell); | |
333 | doorbell_register &= ~SIS_ENABLE_MSIX; | |
334 | ||
335 | writel(doorbell_register, | |
336 | &ctrl_info->registers->sis_host_to_ctrl_doorbell); | |
337 | } | |
338 | ||
339 | void sis_soft_reset(struct pqi_ctrl_info *ctrl_info) | |
340 | { | |
341 | writel(SIS_SOFT_RESET, | |
342 | &ctrl_info->registers->sis_host_to_ctrl_doorbell); | |
343 | } | |
344 | ||
345 | #define SIS_MODE_READY_TIMEOUT_SECS 30 | |
346 | ||
347 | int sis_reenable_sis_mode(struct pqi_ctrl_info *ctrl_info) | |
348 | { | |
349 | int rc; | |
350 | unsigned long timeout; | |
351 | struct pqi_ctrl_registers __iomem *registers; | |
352 | u32 doorbell; | |
353 | ||
354 | registers = ctrl_info->registers; | |
355 | ||
356 | writel(SIS_REENABLE_SIS_MODE, | |
357 | ®isters->sis_host_to_ctrl_doorbell); | |
358 | ||
359 | rc = 0; | |
360 | timeout = (SIS_MODE_READY_TIMEOUT_SECS * HZ) + jiffies; | |
361 | ||
362 | while (1) { | |
363 | doorbell = readl(®isters->sis_ctrl_to_host_doorbell); | |
364 | if ((doorbell & SIS_REENABLE_SIS_MODE) == 0) | |
365 | break; | |
366 | if (time_after(jiffies, timeout)) { | |
367 | rc = -ETIMEDOUT; | |
368 | break; | |
369 | } | |
370 | } | |
371 | ||
372 | if (rc) | |
373 | dev_err(&ctrl_info->pci_dev->dev, | |
374 | "re-enabling SIS mode failed\n"); | |
375 | ||
376 | return rc; | |
377 | } | |
378 | ||
ff6abb73 KB |
379 | void sis_write_driver_scratch(struct pqi_ctrl_info *ctrl_info, u32 value) |
380 | { | |
381 | writel(value, &ctrl_info->registers->sis_driver_scratch); | |
382 | } | |
383 | ||
384 | u32 sis_read_driver_scratch(struct pqi_ctrl_info *ctrl_info) | |
385 | { | |
386 | return readl(&ctrl_info->registers->sis_driver_scratch); | |
387 | } | |
388 | ||
6c223761 KB |
389 | static void __attribute__((unused)) verify_structures(void) |
390 | { | |
391 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
392 | revision) != 0x0); | |
393 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
394 | flags) != 0x4); | |
395 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
396 | error_buffer_paddr_low) != 0x8); | |
397 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
398 | error_buffer_paddr_high) != 0xc); | |
399 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
400 | error_buffer_element_length) != 0x10); | |
401 | BUILD_BUG_ON(offsetof(struct sis_base_struct, | |
402 | error_buffer_num_elements) != 0x14); | |
403 | BUILD_BUG_ON(sizeof(struct sis_base_struct) != 0x18); | |
404 | } |