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