Commit | Line | Data |
---|---|---|
ba3d7ddf CG |
1 | /* |
2 | * dim2_hdm.c - MediaLB DIM2 Hardware Dependent Module | |
3 | * | |
4 | * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG | |
5 | * | |
6 | * This program is distributed in the hope that it will be useful, | |
7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
9 | * GNU General Public License for more details. | |
10 | * | |
11 | * This file is licensed under GPLv2. | |
12 | */ | |
13 | ||
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
15 | ||
16 | #include <linux/module.h> | |
17 | #include <linux/printk.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/interrupt.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/io.h> | |
24 | #include <linux/dma-mapping.h> | |
25 | #include <linux/sched.h> | |
26 | #include <linux/kthread.h> | |
27 | ||
28 | #include <mostcore.h> | |
29 | #include <networking.h> | |
30 | #include "dim2_hal.h" | |
31 | #include "dim2_hdm.h" | |
32 | #include "dim2_errors.h" | |
33 | #include "dim2_sysfs.h" | |
34 | ||
35 | #define DMA_CHANNELS (32 - 1) /* channel 0 is a system channel */ | |
36 | ||
37 | #define MAX_BUFFERS_PACKET 32 | |
38 | #define MAX_BUFFERS_STREAMING 32 | |
39 | #define MAX_BUF_SIZE_PACKET 2048 | |
16dc3743 | 40 | #define MAX_BUF_SIZE_STREAMING (8 * 1024) |
ba3d7ddf CG |
41 | |
42 | /* command line parameter to select clock speed */ | |
43 | static char *clock_speed; | |
44 | module_param(clock_speed, charp, 0); | |
45 | MODULE_PARM_DESC(clock_speed, "MediaLB Clock Speed"); | |
46 | ||
47 | /* | |
48 | * ############################################################################# | |
49 | * | |
50 | * The define below activates an utility function used by HAL-simu | |
51 | * for calling DIM interrupt handler. | |
52 | * It is used only for TEST PURPOSE and shall be commented before release. | |
53 | * | |
54 | * ############################################################################# | |
55 | */ | |
56 | /* #define ENABLE_HDM_TEST */ | |
57 | ||
58 | static DEFINE_SPINLOCK(dim_lock); | |
59 | ||
60 | static void dim2_tasklet_fn(unsigned long data); | |
61 | static DECLARE_TASKLET(dim2_tasklet, dim2_tasklet_fn, 0); | |
62 | ||
63 | /** | |
64 | * struct hdm_channel - private structure to keep channel specific data | |
65 | * @is_initialized: identifier to know whether the channel is initialized | |
66 | * @ch: HAL specific channel data | |
67 | * @pending_list: list to keep MBO's before starting transfer | |
68 | * @started_list: list to keep MBO's after starting transfer | |
69 | * @direction: channel direction (TX or RX) | |
70 | * @data_type: channel data type | |
71 | */ | |
72 | struct hdm_channel { | |
73 | char name[sizeof "caNNN"]; | |
74 | bool is_initialized; | |
75 | struct dim_channel ch; | |
76 | struct list_head pending_list; /* before DIM_EnqueueBuffer() */ | |
77 | struct list_head started_list; /* after DIM_EnqueueBuffer() */ | |
78 | enum most_channel_direction direction; | |
79 | enum most_channel_data_type data_type; | |
80 | }; | |
81 | ||
82 | /** | |
83 | * struct dim2_hdm - private structure to keep interface specific data | |
84 | * @hch: an array of channel specific data | |
85 | * @most_iface: most interface structure | |
86 | * @capabilities: an array of channel capability data | |
87 | * @io_base: I/O register base address | |
88 | * @irq_ahb0: dim2 AHB0 irq number | |
89 | * @clk_speed: user selectable (through command line parameter) clock speed | |
90 | * @netinfo_task: thread to deliver network status | |
91 | * @netinfo_waitq: waitq for the thread to sleep | |
92 | * @deliver_netinfo: to identify whether network status received | |
93 | * @mac_addrs: INIC mac address | |
94 | * @link_state: network link state | |
95 | * @atx_idx: index of async tx channel | |
96 | */ | |
97 | struct dim2_hdm { | |
98 | struct hdm_channel hch[DMA_CHANNELS]; | |
99 | struct most_channel_capability capabilities[DMA_CHANNELS]; | |
100 | struct most_interface most_iface; | |
101 | char name[16 + sizeof "dim2-"]; | |
102 | void *io_base; | |
103 | unsigned int irq_ahb0; | |
104 | int clk_speed; | |
105 | struct task_struct *netinfo_task; | |
106 | wait_queue_head_t netinfo_waitq; | |
107 | int deliver_netinfo; | |
108 | unsigned char mac_addrs[6]; | |
109 | unsigned char link_state; | |
110 | int atx_idx; | |
111 | struct medialb_bus bus; | |
112 | }; | |
113 | ||
114 | #define iface_to_hdm(iface) container_of(iface, struct dim2_hdm, most_iface) | |
115 | ||
116 | /* Macro to identify a network status message */ | |
117 | #define PACKET_IS_NET_INFO(p) \ | |
118 | (((p)[1] == 0x18) && ((p)[2] == 0x05) && ((p)[3] == 0x0C) && \ | |
119 | ((p)[13] == 0x3C) && ((p)[14] == 0x00) && ((p)[15] == 0x0A)) | |
120 | ||
121 | #if defined(ENABLE_HDM_TEST) | |
122 | static struct dim2_hdm *test_dev; | |
123 | #endif | |
124 | ||
125 | bool dim2_sysfs_get_state_cb(void) | |
126 | { | |
127 | bool state; | |
128 | unsigned long flags; | |
129 | ||
130 | spin_lock_irqsave(&dim_lock, flags); | |
b724207b | 131 | state = dim_get_lock_state(); |
ba3d7ddf CG |
132 | spin_unlock_irqrestore(&dim_lock, flags); |
133 | ||
134 | return state; | |
135 | } | |
136 | ||
137 | /** | |
138 | * DIMCB_IoRead - callback from HAL to read an I/O register | |
139 | * @ptr32: register address | |
140 | */ | |
141 | u32 DIMCB_IoRead(u32 *ptr32) | |
142 | { | |
143 | return __raw_readl(ptr32); | |
144 | } | |
145 | ||
146 | /** | |
147 | * DIMCB_IoWrite - callback from HAL to write value to an I/O register | |
148 | * @ptr32: register address | |
149 | * @value: value to write | |
150 | */ | |
151 | void DIMCB_IoWrite(u32 *ptr32, u32 value) | |
152 | { | |
153 | __raw_writel(value, ptr32); | |
154 | } | |
155 | ||
156 | /** | |
de668731 | 157 | * dimcb_on_error - callback from HAL to report miscommunication between |
ba3d7ddf CG |
158 | * HDM and HAL |
159 | * @error_id: Error ID | |
160 | * @error_message: Error message. Some text in a free format | |
161 | */ | |
de668731 | 162 | void dimcb_on_error(u8 error_id, const char *error_message) |
ba3d7ddf | 163 | { |
de668731 | 164 | pr_err("dimcb_on_error: error_id - %d, error_message - %s\n", error_id, |
ba3d7ddf CG |
165 | error_message); |
166 | } | |
167 | ||
ba3d7ddf CG |
168 | /** |
169 | * startup_dim - initialize the dim2 interface | |
170 | * @pdev: platform device | |
171 | * | |
172 | * Get the value of command line parameter "clock_speed" if given or use the | |
173 | * default value, enable the clock and PLL, and initialize the dim2 interface. | |
174 | */ | |
175 | static int startup_dim(struct platform_device *pdev) | |
176 | { | |
177 | struct dim2_hdm *dev = platform_get_drvdata(pdev); | |
178 | struct dim2_platform_data *pdata = pdev->dev.platform_data; | |
179 | u8 hal_ret; | |
180 | ||
181 | dev->clk_speed = -1; | |
182 | ||
183 | if (clock_speed) { | |
184 | if (!strcmp(clock_speed, "256fs")) | |
185 | dev->clk_speed = CLK_256FS; | |
186 | else if (!strcmp(clock_speed, "512fs")) | |
187 | dev->clk_speed = CLK_512FS; | |
188 | else if (!strcmp(clock_speed, "1024fs")) | |
189 | dev->clk_speed = CLK_1024FS; | |
190 | else if (!strcmp(clock_speed, "2048fs")) | |
191 | dev->clk_speed = CLK_2048FS; | |
192 | else if (!strcmp(clock_speed, "3072fs")) | |
193 | dev->clk_speed = CLK_3072FS; | |
194 | else if (!strcmp(clock_speed, "4096fs")) | |
195 | dev->clk_speed = CLK_4096FS; | |
196 | else if (!strcmp(clock_speed, "6144fs")) | |
197 | dev->clk_speed = CLK_6144FS; | |
198 | else if (!strcmp(clock_speed, "8192fs")) | |
199 | dev->clk_speed = CLK_8192FS; | |
200 | } | |
201 | ||
202 | if (dev->clk_speed == -1) { | |
9158d33a | 203 | pr_info("Bad or missing clock speed parameter, using default value: 3072fs\n"); |
ba3d7ddf | 204 | dev->clk_speed = CLK_3072FS; |
9deba73d | 205 | } else { |
ba3d7ddf | 206 | pr_info("Selected clock speed: %s\n", clock_speed); |
9deba73d | 207 | } |
ba3d7ddf CG |
208 | if (pdata && pdata->init) { |
209 | int ret = pdata->init(pdata, dev->io_base, dev->clk_speed); | |
210 | ||
211 | if (ret) | |
212 | return ret; | |
213 | } | |
214 | ||
6417267f | 215 | hal_ret = dim_startup(dev->io_base, dev->clk_speed); |
ba3d7ddf | 216 | if (hal_ret != DIM_NO_ERROR) { |
6417267f | 217 | pr_err("dim_startup failed: %d\n", hal_ret); |
ba3d7ddf CG |
218 | if (pdata && pdata->destroy) |
219 | pdata->destroy(pdata); | |
220 | return -ENODEV; | |
221 | } | |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
226 | /** | |
227 | * try_start_dim_transfer - try to transfer a buffer on a channel | |
228 | * @hdm_ch: channel specific data | |
229 | * | |
230 | * Transfer a buffer from pending_list if the channel is ready | |
231 | */ | |
232 | static int try_start_dim_transfer(struct hdm_channel *hdm_ch) | |
233 | { | |
234 | u16 buf_size; | |
235 | struct list_head *head = &hdm_ch->pending_list; | |
236 | struct mbo *mbo; | |
237 | unsigned long flags; | |
238 | struct dim_ch_state_t st; | |
239 | ||
96d3064b | 240 | BUG_ON(!hdm_ch); |
ba3d7ddf CG |
241 | BUG_ON(!hdm_ch->is_initialized); |
242 | ||
243 | spin_lock_irqsave(&dim_lock, flags); | |
244 | if (list_empty(head)) { | |
245 | spin_unlock_irqrestore(&dim_lock, flags); | |
246 | return -EAGAIN; | |
247 | } | |
248 | ||
249 | if (!DIM_GetChannelState(&hdm_ch->ch, &st)->ready) { | |
250 | spin_unlock_irqrestore(&dim_lock, flags); | |
251 | return -EAGAIN; | |
252 | } | |
253 | ||
254 | mbo = list_entry(head->next, struct mbo, list); | |
255 | buf_size = mbo->buffer_length; | |
256 | ||
257 | BUG_ON(mbo->bus_address == 0); | |
258 | if (!DIM_EnqueueBuffer(&hdm_ch->ch, mbo->bus_address, buf_size)) { | |
259 | list_del(head->next); | |
260 | spin_unlock_irqrestore(&dim_lock, flags); | |
261 | mbo->processed_length = 0; | |
262 | mbo->status = MBO_E_INVAL; | |
263 | mbo->complete(mbo); | |
264 | return -EFAULT; | |
265 | } | |
266 | ||
267 | list_move_tail(head->next, &hdm_ch->started_list); | |
268 | spin_unlock_irqrestore(&dim_lock, flags); | |
269 | ||
270 | return 0; | |
271 | } | |
272 | ||
273 | /** | |
274 | * deliver_netinfo_thread - thread to deliver network status to mostcore | |
275 | * @data: private data | |
276 | * | |
277 | * Wait for network status and deliver it to mostcore once it is received | |
278 | */ | |
279 | static int deliver_netinfo_thread(void *data) | |
280 | { | |
98b5afd8 | 281 | struct dim2_hdm *dev = data; |
ba3d7ddf CG |
282 | |
283 | while (!kthread_should_stop()) { | |
284 | wait_event_interruptible(dev->netinfo_waitq, | |
285 | dev->deliver_netinfo || | |
286 | kthread_should_stop()); | |
287 | ||
288 | if (dev->deliver_netinfo) { | |
289 | dev->deliver_netinfo--; | |
290 | most_deliver_netinfo(&dev->most_iface, dev->link_state, | |
291 | dev->mac_addrs); | |
292 | } | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
298 | /** | |
299 | * retrieve_netinfo - retrieve network status from received buffer | |
300 | * @dev: private data | |
301 | * @mbo: received MBO | |
302 | * | |
303 | * Parse the message in buffer and get node address, link state, MAC address. | |
304 | * Wake up a thread to deliver this status to mostcore | |
305 | */ | |
306 | static void retrieve_netinfo(struct dim2_hdm *dev, struct mbo *mbo) | |
307 | { | |
308 | u8 *data = mbo->virt_address; | |
309 | u8 *mac = dev->mac_addrs; | |
310 | ||
311 | pr_info("Node Address: 0x%03x\n", (u16)data[16] << 8 | data[17]); | |
312 | dev->link_state = data[18]; | |
313 | pr_info("NIState: %d\n", dev->link_state); | |
314 | memcpy(mac, data + 19, 6); | |
315 | pr_info("MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n", | |
316 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | |
317 | dev->deliver_netinfo++; | |
318 | wake_up_interruptible(&dev->netinfo_waitq); | |
319 | } | |
320 | ||
321 | /** | |
322 | * service_done_flag - handle completed buffers | |
323 | * @dev: private data | |
324 | * @ch_idx: channel index | |
325 | * | |
326 | * Return back the completed buffers to mostcore, using completion callback | |
327 | */ | |
328 | static void service_done_flag(struct dim2_hdm *dev, int ch_idx) | |
329 | { | |
330 | struct hdm_channel *hdm_ch = dev->hch + ch_idx; | |
331 | struct dim_ch_state_t st; | |
332 | struct list_head *head; | |
333 | struct mbo *mbo; | |
334 | int done_buffers; | |
335 | unsigned long flags; | |
336 | u8 *data; | |
337 | ||
96d3064b | 338 | BUG_ON(!hdm_ch); |
ba3d7ddf CG |
339 | BUG_ON(!hdm_ch->is_initialized); |
340 | ||
341 | spin_lock_irqsave(&dim_lock, flags); | |
342 | ||
343 | done_buffers = DIM_GetChannelState(&hdm_ch->ch, &st)->done_buffers; | |
344 | if (!done_buffers) { | |
345 | spin_unlock_irqrestore(&dim_lock, flags); | |
346 | return; | |
347 | } | |
348 | ||
38c38544 | 349 | if (!dim_detach_buffers(&hdm_ch->ch, done_buffers)) { |
ba3d7ddf CG |
350 | spin_unlock_irqrestore(&dim_lock, flags); |
351 | return; | |
352 | } | |
353 | spin_unlock_irqrestore(&dim_lock, flags); | |
354 | ||
355 | head = &hdm_ch->started_list; | |
356 | ||
357 | while (done_buffers) { | |
358 | spin_lock_irqsave(&dim_lock, flags); | |
359 | if (list_empty(head)) { | |
360 | spin_unlock_irqrestore(&dim_lock, flags); | |
9158d33a | 361 | pr_crit("hard error: started_mbo list is empty whereas DIM2 has sent buffers\n"); |
ba3d7ddf CG |
362 | break; |
363 | } | |
364 | ||
365 | mbo = list_entry(head->next, struct mbo, list); | |
366 | list_del(head->next); | |
367 | spin_unlock_irqrestore(&dim_lock, flags); | |
368 | ||
369 | data = mbo->virt_address; | |
370 | ||
371 | if (hdm_ch->data_type == MOST_CH_ASYNC && | |
372 | hdm_ch->direction == MOST_CH_RX && | |
373 | PACKET_IS_NET_INFO(data)) { | |
ba3d7ddf CG |
374 | retrieve_netinfo(dev, mbo); |
375 | ||
376 | spin_lock_irqsave(&dim_lock, flags); | |
377 | list_add_tail(&mbo->list, &hdm_ch->pending_list); | |
378 | spin_unlock_irqrestore(&dim_lock, flags); | |
379 | } else { | |
380 | if (hdm_ch->data_type == MOST_CH_CONTROL || | |
381 | hdm_ch->data_type == MOST_CH_ASYNC) { | |
ba3d7ddf CG |
382 | u32 const data_size = |
383 | (u32)data[0] * 256 + data[1] + 2; | |
384 | ||
385 | mbo->processed_length = | |
7f9cacb6 CG |
386 | min_t(u32, data_size, |
387 | mbo->buffer_length); | |
ba3d7ddf CG |
388 | } else { |
389 | mbo->processed_length = mbo->buffer_length; | |
390 | } | |
391 | mbo->status = MBO_SUCCESS; | |
392 | mbo->complete(mbo); | |
393 | } | |
394 | ||
395 | done_buffers--; | |
396 | } | |
397 | } | |
398 | ||
399 | static struct dim_channel **get_active_channels(struct dim2_hdm *dev, | |
edaa1e33 | 400 | struct dim_channel **buffer) |
ba3d7ddf CG |
401 | { |
402 | int idx = 0; | |
403 | int ch_idx; | |
404 | ||
405 | for (ch_idx = 0; ch_idx < DMA_CHANNELS; ch_idx++) { | |
406 | if (dev->hch[ch_idx].is_initialized) | |
407 | buffer[idx++] = &dev->hch[ch_idx].ch; | |
408 | } | |
96d3064b | 409 | buffer[idx++] = NULL; |
ba3d7ddf CG |
410 | |
411 | return buffer; | |
412 | } | |
413 | ||
414 | /** | |
415 | * dim2_tasklet_fn - tasklet function | |
416 | * @data: private data | |
417 | * | |
418 | * Service each initialized channel, if needed | |
419 | */ | |
420 | static void dim2_tasklet_fn(unsigned long data) | |
421 | { | |
422 | struct dim2_hdm *dev = (struct dim2_hdm *)data; | |
423 | unsigned long flags; | |
424 | int ch_idx; | |
425 | ||
426 | for (ch_idx = 0; ch_idx < DMA_CHANNELS; ch_idx++) { | |
427 | if (!dev->hch[ch_idx].is_initialized) | |
428 | continue; | |
429 | ||
430 | spin_lock_irqsave(&dim_lock, flags); | |
df8da2e3 | 431 | DIM_ServiceChannel(&dev->hch[ch_idx].ch); |
ba3d7ddf CG |
432 | spin_unlock_irqrestore(&dim_lock, flags); |
433 | ||
434 | service_done_flag(dev, ch_idx); | |
435 | while (!try_start_dim_transfer(dev->hch + ch_idx)) | |
436 | continue; | |
437 | } | |
438 | } | |
439 | ||
440 | /** | |
441 | * dim2_ahb_isr - interrupt service routine | |
442 | * @irq: irq number | |
443 | * @_dev: private data | |
444 | * | |
445 | * Acknowledge the interrupt and schedule a tasklet to service channels. | |
446 | * Return IRQ_HANDLED. | |
447 | */ | |
448 | static irqreturn_t dim2_ahb_isr(int irq, void *_dev) | |
449 | { | |
98b5afd8 | 450 | struct dim2_hdm *dev = _dev; |
ba3d7ddf CG |
451 | struct dim_channel *buffer[DMA_CHANNELS + 1]; |
452 | unsigned long flags; | |
453 | ||
454 | spin_lock_irqsave(&dim_lock, flags); | |
455 | DIM_ServiceIrq(get_active_channels(dev, buffer)); | |
456 | spin_unlock_irqrestore(&dim_lock, flags); | |
457 | ||
458 | #if !defined(ENABLE_HDM_TEST) | |
459 | dim2_tasklet.data = (unsigned long)dev; | |
460 | tasklet_schedule(&dim2_tasklet); | |
461 | #else | |
462 | dim2_tasklet_fn((unsigned long)dev); | |
463 | #endif | |
464 | return IRQ_HANDLED; | |
465 | } | |
466 | ||
467 | #if defined(ENABLE_HDM_TEST) | |
468 | ||
469 | /* | |
470 | * Utility function used by HAL-simu for calling DIM interrupt handler. | |
471 | * It is used only for TEST PURPOSE. | |
472 | */ | |
473 | void raise_dim_interrupt(void) | |
474 | { | |
475 | (void)dim2_ahb_isr(0, test_dev); | |
476 | } | |
477 | #endif | |
478 | ||
479 | /** | |
480 | * complete_all_mbos - complete MBO's in a list | |
481 | * @head: list head | |
482 | * | |
483 | * Delete all the entries in list and return back MBO's to mostcore using | |
484 | * completion call back. | |
485 | */ | |
486 | static void complete_all_mbos(struct list_head *head) | |
487 | { | |
488 | unsigned long flags; | |
489 | struct mbo *mbo; | |
490 | ||
491 | for (;;) { | |
492 | spin_lock_irqsave(&dim_lock, flags); | |
493 | if (list_empty(head)) { | |
494 | spin_unlock_irqrestore(&dim_lock, flags); | |
495 | break; | |
496 | } | |
497 | ||
498 | mbo = list_entry(head->next, struct mbo, list); | |
499 | list_del(head->next); | |
500 | spin_unlock_irqrestore(&dim_lock, flags); | |
501 | ||
502 | mbo->processed_length = 0; | |
503 | mbo->status = MBO_E_CLOSE; | |
504 | mbo->complete(mbo); | |
505 | } | |
506 | } | |
507 | ||
508 | /** | |
509 | * configure_channel - initialize a channel | |
510 | * @iface: interface the channel belongs to | |
511 | * @channel: channel to be configured | |
512 | * @channel_config: structure that holds the configuration information | |
513 | * | |
514 | * Receives configuration information from mostcore and initialize | |
515 | * the corresponding channel. Return 0 on success, negative on failure. | |
516 | */ | |
517 | static int configure_channel(struct most_interface *most_iface, int ch_idx, | |
518 | struct most_channel_config *ccfg) | |
519 | { | |
520 | struct dim2_hdm *dev = iface_to_hdm(most_iface); | |
521 | bool const is_tx = ccfg->direction == MOST_CH_TX; | |
522 | u16 const sub_size = ccfg->subbuffer_size; | |
523 | u16 const buf_size = ccfg->buffer_size; | |
524 | u16 new_size; | |
525 | unsigned long flags; | |
526 | u8 hal_ret; | |
527 | int const ch_addr = ch_idx * 2 + 2; | |
528 | struct hdm_channel *const hdm_ch = dev->hch + ch_idx; | |
529 | ||
530 | BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); | |
531 | ||
532 | if (hdm_ch->is_initialized) | |
533 | return -EPERM; | |
534 | ||
535 | switch (ccfg->data_type) { | |
536 | case MOST_CH_CONTROL: | |
537 | new_size = DIM_NormCtrlAsyncBufferSize(buf_size); | |
538 | if (new_size == 0) { | |
539 | pr_err("%s: too small buffer size\n", hdm_ch->name); | |
540 | return -EINVAL; | |
541 | } | |
542 | ccfg->buffer_size = new_size; | |
543 | if (new_size != buf_size) | |
544 | pr_warn("%s: fixed buffer size (%d -> %d)\n", | |
545 | hdm_ch->name, buf_size, new_size); | |
546 | spin_lock_irqsave(&dim_lock, flags); | |
9158d33a CG |
547 | hal_ret = DIM_InitControl(&hdm_ch->ch, is_tx, ch_addr, |
548 | buf_size); | |
ba3d7ddf CG |
549 | break; |
550 | case MOST_CH_ASYNC: | |
551 | new_size = DIM_NormCtrlAsyncBufferSize(buf_size); | |
552 | if (new_size == 0) { | |
553 | pr_err("%s: too small buffer size\n", hdm_ch->name); | |
554 | return -EINVAL; | |
555 | } | |
556 | ccfg->buffer_size = new_size; | |
557 | if (new_size != buf_size) | |
558 | pr_warn("%s: fixed buffer size (%d -> %d)\n", | |
559 | hdm_ch->name, buf_size, new_size); | |
560 | spin_lock_irqsave(&dim_lock, flags); | |
561 | hal_ret = DIM_InitAsync(&hdm_ch->ch, is_tx, ch_addr, buf_size); | |
562 | break; | |
563 | case MOST_CH_ISOC_AVP: | |
564 | new_size = DIM_NormIsocBufferSize(buf_size, sub_size); | |
565 | if (new_size == 0) { | |
9158d33a CG |
566 | pr_err("%s: invalid sub-buffer size or too small buffer size\n", |
567 | hdm_ch->name); | |
ba3d7ddf CG |
568 | return -EINVAL; |
569 | } | |
570 | ccfg->buffer_size = new_size; | |
571 | if (new_size != buf_size) | |
572 | pr_warn("%s: fixed buffer size (%d -> %d)\n", | |
573 | hdm_ch->name, buf_size, new_size); | |
574 | spin_lock_irqsave(&dim_lock, flags); | |
575 | hal_ret = DIM_InitIsoc(&hdm_ch->ch, is_tx, ch_addr, sub_size); | |
576 | break; | |
577 | case MOST_CH_SYNC: | |
578 | new_size = DIM_NormSyncBufferSize(buf_size, sub_size); | |
579 | if (new_size == 0) { | |
9158d33a CG |
580 | pr_err("%s: invalid sub-buffer size or too small buffer size\n", |
581 | hdm_ch->name); | |
ba3d7ddf CG |
582 | return -EINVAL; |
583 | } | |
584 | ccfg->buffer_size = new_size; | |
585 | if (new_size != buf_size) | |
586 | pr_warn("%s: fixed buffer size (%d -> %d)\n", | |
587 | hdm_ch->name, buf_size, new_size); | |
588 | spin_lock_irqsave(&dim_lock, flags); | |
589 | hal_ret = DIM_InitSync(&hdm_ch->ch, is_tx, ch_addr, sub_size); | |
590 | break; | |
591 | default: | |
592 | pr_err("%s: configure failed, bad channel type: %d\n", | |
593 | hdm_ch->name, ccfg->data_type); | |
594 | return -EINVAL; | |
595 | } | |
596 | ||
597 | if (hal_ret != DIM_NO_ERROR) { | |
598 | spin_unlock_irqrestore(&dim_lock, flags); | |
599 | pr_err("%s: configure failed (%d), type: %d, is_tx: %d\n", | |
600 | hdm_ch->name, hal_ret, ccfg->data_type, (int)is_tx); | |
601 | return -ENODEV; | |
602 | } | |
603 | ||
604 | hdm_ch->data_type = ccfg->data_type; | |
605 | hdm_ch->direction = ccfg->direction; | |
606 | hdm_ch->is_initialized = true; | |
607 | ||
608 | if (hdm_ch->data_type == MOST_CH_ASYNC && | |
609 | hdm_ch->direction == MOST_CH_TX && | |
610 | dev->atx_idx < 0) | |
611 | dev->atx_idx = ch_idx; | |
612 | ||
613 | spin_unlock_irqrestore(&dim_lock, flags); | |
614 | ||
615 | return 0; | |
616 | } | |
617 | ||
618 | /** | |
619 | * enqueue - enqueue a buffer for data transfer | |
620 | * @iface: intended interface | |
621 | * @channel: ID of the channel the buffer is intended for | |
622 | * @mbo: pointer to the buffer object | |
623 | * | |
624 | * Push the buffer into pending_list and try to transfer one buffer from | |
625 | * pending_list. Return 0 on success, negative on failure. | |
626 | */ | |
627 | static int enqueue(struct most_interface *most_iface, int ch_idx, | |
628 | struct mbo *mbo) | |
629 | { | |
630 | struct dim2_hdm *dev = iface_to_hdm(most_iface); | |
631 | struct hdm_channel *hdm_ch = dev->hch + ch_idx; | |
632 | unsigned long flags; | |
633 | ||
634 | BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); | |
635 | ||
636 | if (!hdm_ch->is_initialized) | |
637 | return -EPERM; | |
638 | ||
639 | if (mbo->bus_address == 0) | |
640 | return -EFAULT; | |
641 | ||
642 | spin_lock_irqsave(&dim_lock, flags); | |
643 | list_add_tail(&mbo->list, &hdm_ch->pending_list); | |
644 | spin_unlock_irqrestore(&dim_lock, flags); | |
645 | ||
646 | (void)try_start_dim_transfer(hdm_ch); | |
647 | ||
648 | return 0; | |
649 | } | |
650 | ||
651 | /** | |
652 | * request_netinfo - triggers retrieving of network info | |
653 | * @iface: pointer to the interface | |
654 | * @channel_id: corresponding channel ID | |
655 | * | |
656 | * Send a command to INIC which triggers retrieving of network info by means of | |
657 | * "Message exchange over MDP/MEP". Return 0 on success, negative on failure. | |
658 | */ | |
659 | static void request_netinfo(struct most_interface *most_iface, int ch_idx) | |
660 | { | |
661 | struct dim2_hdm *dev = iface_to_hdm(most_iface); | |
662 | struct mbo *mbo; | |
663 | u8 *data; | |
664 | ||
665 | if (dev->atx_idx < 0) { | |
666 | pr_err("Async Tx Not initialized\n"); | |
667 | return; | |
668 | } | |
669 | ||
71457d48 | 670 | mbo = most_get_mbo(&dev->most_iface, dev->atx_idx, NULL); |
ba3d7ddf CG |
671 | if (!mbo) |
672 | return; | |
673 | ||
674 | mbo->buffer_length = 5; | |
675 | ||
676 | data = mbo->virt_address; | |
677 | ||
678 | data[0] = 0x00; /* PML High byte */ | |
679 | data[1] = 0x03; /* PML Low byte */ | |
680 | data[2] = 0x02; /* PMHL */ | |
681 | data[3] = 0x08; /* FPH */ | |
682 | data[4] = 0x40; /* FMF (FIFO cmd msg - Triggers NAOverMDP) */ | |
683 | ||
684 | most_submit_mbo(mbo); | |
685 | } | |
686 | ||
687 | /** | |
688 | * poison_channel - poison buffers of a channel | |
689 | * @iface: pointer to the interface the channel to be poisoned belongs to | |
690 | * @channel_id: corresponding channel ID | |
691 | * | |
692 | * Destroy a channel and complete all the buffers in both started_list & | |
693 | * pending_list. Return 0 on success, negative on failure. | |
694 | */ | |
695 | static int poison_channel(struct most_interface *most_iface, int ch_idx) | |
696 | { | |
697 | struct dim2_hdm *dev = iface_to_hdm(most_iface); | |
698 | struct hdm_channel *hdm_ch = dev->hch + ch_idx; | |
699 | unsigned long flags; | |
700 | u8 hal_ret; | |
701 | int ret = 0; | |
702 | ||
703 | BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); | |
704 | ||
705 | if (!hdm_ch->is_initialized) | |
706 | return -EPERM; | |
707 | ||
708 | spin_lock_irqsave(&dim_lock, flags); | |
709 | hal_ret = DIM_DestroyChannel(&hdm_ch->ch); | |
710 | hdm_ch->is_initialized = false; | |
711 | if (ch_idx == dev->atx_idx) | |
712 | dev->atx_idx = -1; | |
713 | spin_unlock_irqrestore(&dim_lock, flags); | |
714 | if (hal_ret != DIM_NO_ERROR) { | |
715 | pr_err("HAL Failed to close channel %s\n", hdm_ch->name); | |
716 | ret = -EFAULT; | |
717 | } | |
718 | ||
719 | complete_all_mbos(&hdm_ch->started_list); | |
720 | complete_all_mbos(&hdm_ch->pending_list); | |
721 | ||
722 | return ret; | |
723 | } | |
724 | ||
725 | /* | |
726 | * dim2_probe - dim2 probe handler | |
727 | * @pdev: platform device structure | |
728 | * | |
729 | * Register the dim2 interface with mostcore and initialize it. | |
730 | * Return 0 on success, negative on failure. | |
731 | */ | |
732 | static int dim2_probe(struct platform_device *pdev) | |
733 | { | |
734 | struct dim2_hdm *dev; | |
735 | struct resource *res; | |
736 | int ret, i; | |
737 | struct kobject *kobj; | |
738 | ||
739 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
740 | if (!dev) | |
741 | return -ENOMEM; | |
742 | ||
743 | dev->atx_idx = -1; | |
744 | ||
745 | platform_set_drvdata(pdev, dev); | |
746 | #if defined(ENABLE_HDM_TEST) | |
747 | test_dev = dev; | |
748 | #else | |
749 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
750 | if (!res) { | |
751 | pr_err("no memory region defined\n"); | |
752 | ret = -ENOENT; | |
753 | goto err_free_dev; | |
754 | } | |
755 | ||
756 | if (!request_mem_region(res->start, resource_size(res), pdev->name)) { | |
757 | pr_err("failed to request mem region\n"); | |
758 | ret = -EBUSY; | |
759 | goto err_free_dev; | |
760 | } | |
761 | ||
762 | dev->io_base = ioremap(res->start, resource_size(res)); | |
763 | if (!dev->io_base) { | |
764 | pr_err("failed to ioremap\n"); | |
765 | ret = -ENOMEM; | |
766 | goto err_release_mem; | |
767 | } | |
768 | ||
769 | ret = platform_get_irq(pdev, 0); | |
770 | if (ret < 0) { | |
771 | pr_err("failed to get irq\n"); | |
772 | goto err_unmap_io; | |
773 | } | |
774 | dev->irq_ahb0 = ret; | |
775 | ||
776 | ret = request_irq(dev->irq_ahb0, dim2_ahb_isr, 0, "mlb_ahb0", dev); | |
777 | if (ret) { | |
9158d33a CG |
778 | pr_err("failed to request IRQ: %d, err: %d\n", |
779 | dev->irq_ahb0, ret); | |
ba3d7ddf CG |
780 | goto err_unmap_io; |
781 | } | |
782 | #endif | |
783 | init_waitqueue_head(&dev->netinfo_waitq); | |
784 | dev->deliver_netinfo = 0; | |
785 | dev->netinfo_task = kthread_run(&deliver_netinfo_thread, (void *)dev, | |
786 | "dim2_netinfo"); | |
787 | if (IS_ERR(dev->netinfo_task)) { | |
788 | ret = PTR_ERR(dev->netinfo_task); | |
789 | goto err_free_irq; | |
790 | } | |
791 | ||
792 | for (i = 0; i < DMA_CHANNELS; i++) { | |
793 | struct most_channel_capability *cap = dev->capabilities + i; | |
794 | struct hdm_channel *hdm_ch = dev->hch + i; | |
795 | ||
796 | INIT_LIST_HEAD(&hdm_ch->pending_list); | |
797 | INIT_LIST_HEAD(&hdm_ch->started_list); | |
798 | hdm_ch->is_initialized = false; | |
799 | snprintf(hdm_ch->name, sizeof(hdm_ch->name), "ca%d", i * 2 + 2); | |
800 | ||
801 | cap->name_suffix = hdm_ch->name; | |
802 | cap->direction = MOST_CH_RX | MOST_CH_TX; | |
803 | cap->data_type = MOST_CH_CONTROL | MOST_CH_ASYNC | | |
804 | MOST_CH_ISOC_AVP | MOST_CH_SYNC; | |
805 | cap->num_buffers_packet = MAX_BUFFERS_PACKET; | |
806 | cap->buffer_size_packet = MAX_BUF_SIZE_PACKET; | |
807 | cap->num_buffers_streaming = MAX_BUFFERS_STREAMING; | |
808 | cap->buffer_size_streaming = MAX_BUF_SIZE_STREAMING; | |
809 | } | |
810 | ||
811 | { | |
812 | const char *fmt; | |
813 | ||
814 | if (sizeof(res->start) == sizeof(long long)) | |
815 | fmt = "dim2-%016llx"; | |
816 | else if (sizeof(res->start) == sizeof(long)) | |
817 | fmt = "dim2-%016lx"; | |
818 | else | |
819 | fmt = "dim2-%016x"; | |
820 | ||
821 | snprintf(dev->name, sizeof(dev->name), fmt, res->start); | |
822 | } | |
823 | ||
824 | dev->most_iface.interface = ITYPE_MEDIALB_DIM2; | |
825 | dev->most_iface.description = dev->name; | |
826 | dev->most_iface.num_channels = DMA_CHANNELS; | |
827 | dev->most_iface.channel_vector = dev->capabilities; | |
828 | dev->most_iface.configure = configure_channel; | |
829 | dev->most_iface.enqueue = enqueue; | |
830 | dev->most_iface.poison_channel = poison_channel; | |
831 | dev->most_iface.request_netinfo = request_netinfo; | |
832 | ||
833 | kobj = most_register_interface(&dev->most_iface); | |
834 | if (IS_ERR(kobj)) { | |
835 | ret = PTR_ERR(kobj); | |
836 | pr_err("failed to register MOST interface\n"); | |
837 | goto err_stop_thread; | |
838 | } | |
839 | ||
840 | ret = dim2_sysfs_probe(&dev->bus, kobj); | |
841 | if (ret) | |
842 | goto err_unreg_iface; | |
843 | ||
844 | ret = startup_dim(pdev); | |
845 | if (ret) { | |
846 | pr_err("failed to initialize DIM2\n"); | |
847 | goto err_destroy_bus; | |
848 | } | |
849 | ||
850 | return 0; | |
851 | ||
852 | err_destroy_bus: | |
853 | dim2_sysfs_destroy(&dev->bus); | |
854 | err_unreg_iface: | |
855 | most_deregister_interface(&dev->most_iface); | |
856 | err_stop_thread: | |
857 | kthread_stop(dev->netinfo_task); | |
858 | err_free_irq: | |
859 | #if !defined(ENABLE_HDM_TEST) | |
860 | free_irq(dev->irq_ahb0, dev); | |
861 | err_unmap_io: | |
862 | iounmap(dev->io_base); | |
863 | err_release_mem: | |
864 | release_mem_region(res->start, resource_size(res)); | |
865 | err_free_dev: | |
866 | #endif | |
867 | kfree(dev); | |
868 | ||
869 | return ret; | |
870 | } | |
871 | ||
872 | /** | |
873 | * dim2_remove - dim2 remove handler | |
874 | * @pdev: platform device structure | |
875 | * | |
876 | * Unregister the interface from mostcore | |
877 | */ | |
878 | static int dim2_remove(struct platform_device *pdev) | |
879 | { | |
880 | struct dim2_hdm *dev = platform_get_drvdata(pdev); | |
881 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
882 | struct dim2_platform_data *pdata = pdev->dev.platform_data; | |
883 | unsigned long flags; | |
884 | ||
885 | spin_lock_irqsave(&dim_lock, flags); | |
50a45b17 | 886 | dim_shutdown(); |
ba3d7ddf CG |
887 | spin_unlock_irqrestore(&dim_lock, flags); |
888 | ||
889 | if (pdata && pdata->destroy) | |
890 | pdata->destroy(pdata); | |
891 | ||
892 | dim2_sysfs_destroy(&dev->bus); | |
893 | most_deregister_interface(&dev->most_iface); | |
894 | kthread_stop(dev->netinfo_task); | |
895 | #if !defined(ENABLE_HDM_TEST) | |
896 | free_irq(dev->irq_ahb0, dev); | |
897 | iounmap(dev->io_base); | |
898 | release_mem_region(res->start, resource_size(res)); | |
899 | #endif | |
900 | kfree(dev); | |
901 | platform_set_drvdata(pdev, NULL); | |
902 | ||
903 | /* | |
904 | * break link to local platform_device_id struct | |
905 | * to prevent crash by unload platform device module | |
906 | */ | |
96d3064b | 907 | pdev->id_entry = NULL; |
ba3d7ddf CG |
908 | |
909 | return 0; | |
910 | } | |
911 | ||
912 | static struct platform_device_id dim2_id[] = { | |
913 | { "medialb_dim2" }, | |
914 | { }, /* Terminating entry */ | |
915 | }; | |
916 | ||
917 | MODULE_DEVICE_TABLE(platform, dim2_id); | |
918 | ||
919 | static struct platform_driver dim2_driver = { | |
920 | .probe = dim2_probe, | |
921 | .remove = dim2_remove, | |
922 | .id_table = dim2_id, | |
923 | .driver = { | |
924 | .name = "hdm_dim2", | |
ba3d7ddf CG |
925 | }, |
926 | }; | |
927 | ||
8668984f | 928 | module_platform_driver(dim2_driver); |
ba3d7ddf CG |
929 | |
930 | MODULE_AUTHOR("Jain Roy Ambi <JainRoy.Ambi@microchip.com>"); | |
931 | MODULE_AUTHOR("Andrey Shvetsov <andrey.shvetsov@k2l.de>"); | |
932 | MODULE_DESCRIPTION("MediaLB DIM2 Hardware Dependent Module"); | |
933 | MODULE_LICENSE("GPL"); |