/* Cypress West Bridge API source file (cyaslowlevel.c) ## =========================== ## Copyright (C) 2010 Cypress Semiconductor ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 2 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor ## Boston, MA 02110-1301, USA. ## =========================== */ #include "../../include/linux/westbridge/cyashal.h" #include "../../include/linux/westbridge/cyascast.h" #include "../../include/linux/westbridge/cyasdevice.h" #include "../../include/linux/westbridge/cyaslowlevel.h" #include "../../include/linux/westbridge/cyasintr.h" #include "../../include/linux/westbridge/cyaserr.h" #include "../../include/linux/westbridge/cyasregs.h" static const uint32_t cy_as_low_level_timeout_count = 65536 * 4; /* Forward declaration */ static cy_as_return_status_t cy_as_send_one(cy_as_device *dev_p, cy_as_ll_request_response *req_p); /* * This array holds the size of the largest request we will ever recevie from * the West Bridge device per context. The size is in 16 bit words. Note a * size of 0xffff indicates that there will be no requests on this context * from West Bridge. */ static uint16_t max_request_length[CY_RQT_CONTEXT_COUNT] = { 8, /* CY_RQT_GENERAL_RQT_CONTEXT - CY_RQT_INITIALIZATION_COMPLETE */ 8, /* CY_RQT_RESOURCE_RQT_CONTEXT - none */ 8, /* CY_RQT_STORAGE_RQT_CONTEXT - CY_RQT_MEDIA_CHANGED */ 128, /* CY_RQT_USB_RQT_CONTEXT - CY_RQT_USB_EVENT */ 8 /* CY_RQT_TUR_RQT_CONTEXT - CY_RQT_TURBO_CMD_FROM_HOST */ }; /* * For the given context, this function removes the request node at the head * of the queue from the context. This is called after all processing has * occurred on the given request and response and we are ready to remove this * entry from the queue. */ static void cy_as_ll_remove_request_queue_head(cy_as_device *dev_p, cy_as_context *ctxt_p) { uint32_t mask, state; cy_as_ll_request_list_node *node_p; (void)dev_p; cy_as_hal_assert(ctxt_p->request_queue_p != 0); mask = cy_as_hal_disable_interrupts(); node_p = ctxt_p->request_queue_p; ctxt_p->request_queue_p = node_p->next; cy_as_hal_enable_interrupts(mask); node_p->callback = 0; node_p->rqt = 0; node_p->resp = 0; /* * note that the caller allocates and destroys the request and * response. generally the destroy happens in the callback for * async requests and after the wait returns for sync. the * request and response may not actually be destroyed but may be * managed in other ways as well. it is the responsibilty of * the caller to deal with these in any case. the caller can do * this in the request/response callback function. */ state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(node_p); cy_as_hal_enable_interrupts(state); } /* * For the context given, this function sends the next request to * West Bridge via the mailbox register, if the next request is * ready to be sent and has not already been sent. */ static void cy_as_ll_send_next_request(cy_as_device *dev_p, cy_as_context *ctxt_p) { cy_as_return_status_t ret = CY_AS_ERROR_SUCCESS; /* * ret == ret is equivalent to while (1) but eliminates compiler * warnings for some compilers. */ while (ret == ret) { cy_as_ll_request_list_node *node_p = ctxt_p->request_queue_p; if (node_p == 0) break; if (cy_as_request_get_node_state(node_p) != CY_AS_REQUEST_LIST_STATE_QUEUED) break; cy_as_request_set_node_state(node_p, CY_AS_REQUEST_LIST_STATE_WAITING); ret = cy_as_send_one(dev_p, node_p->rqt); if (ret == CY_AS_ERROR_SUCCESS) break; /* * if an error occurs in sending the request, tell the requester * about the error and remove the request from the queue. */ cy_as_request_set_node_state(node_p, CY_AS_REQUEST_LIST_STATE_RECEIVED); node_p->callback(dev_p, ctxt_p->number, node_p->rqt, node_p->resp, ret); cy_as_ll_remove_request_queue_head(dev_p, ctxt_p); /* * this falls through to the while loop to send the next request * since the previous request did not get sent. */ } } /* * This method removes an entry from the request queue of a given context. * The entry is removed only if it is not in transit. */ cy_as_remove_request_result_t cy_as_ll_remove_request(cy_as_device *dev_p, cy_as_context *ctxt_p, cy_as_ll_request_response *req_p, cy_bool force) { uint32_t imask; cy_as_ll_request_list_node *node_p; cy_as_ll_request_list_node *tmp_p; uint32_t state; imask = cy_as_hal_disable_interrupts(); if (ctxt_p->request_queue_p != 0 && ctxt_p->request_queue_p->rqt == req_p) { node_p = ctxt_p->request_queue_p; if ((cy_as_request_get_node_state(node_p) == CY_AS_REQUEST_LIST_STATE_WAITING) && (!force)) { cy_as_hal_enable_interrupts(imask); return cy_as_remove_request_in_transit; } ctxt_p->request_queue_p = node_p->next; } else { tmp_p = ctxt_p->request_queue_p; while (tmp_p != 0 && tmp_p->next != 0 && tmp_p->next->rqt != req_p) tmp_p = tmp_p->next; if (tmp_p == 0 || tmp_p->next == 0) { cy_as_hal_enable_interrupts(imask); return cy_as_remove_request_not_found; } node_p = tmp_p->next; tmp_p->next = node_p->next; } if (node_p->callback) node_p->callback(dev_p, ctxt_p->number, node_p->rqt, node_p->resp, CY_AS_ERROR_CANCELED); state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(node_p); cy_as_hal_enable_interrupts(state); cy_as_hal_enable_interrupts(imask); return cy_as_remove_request_sucessful; } void cy_as_ll_remove_all_requests(cy_as_device *dev_p, cy_as_context *ctxt_p) { cy_as_ll_request_list_node *node = ctxt_p->request_queue_p; while (node) { if (cy_as_request_get_node_state(ctxt_p->request_queue_p) != CY_AS_REQUEST_LIST_STATE_RECEIVED) cy_as_ll_remove_request(dev_p, ctxt_p, node->rqt, cy_true); node = node->next; } } static cy_bool cy_as_ll_is_in_queue(cy_as_context *ctxt_p, cy_as_ll_request_response *req_p) { uint32_t mask; cy_as_ll_request_list_node *node_p; mask = cy_as_hal_disable_interrupts(); node_p = ctxt_p->request_queue_p; while (node_p) { if (node_p->rqt == req_p) { cy_as_hal_enable_interrupts(mask); return cy_true; } node_p = node_p->next; } cy_as_hal_enable_interrupts(mask); return cy_false; } /* * This is the handler for mailbox data when we are trying to send data * to the West Bridge firmware. The firmware may be trying to send us * data and we need to queue this data to allow the firmware to move * forward and be in a state to receive our request. Here we just queue * the data and it is processed at a later time by the mailbox interrupt * handler. */ void cy_as_ll_queue_mailbox_data(cy_as_device *dev_p) { cy_as_context *ctxt_p; uint8_t context; uint16_t data[4]; int32_t i; /* Read the data from mailbox 0 to determine what to do with the data */ for (i = 3; i >= 0; i--) data[i] = cy_as_hal_read_register(dev_p->tag, cy_cast_int2U_int16(CY_AS_MEM_P0_MAILBOX0 + i)); context = cy_as_mbox_get_context(data[0]); if (context >= CY_RQT_CONTEXT_COUNT) { cy_as_hal_print_message("mailbox request/response received " "with invalid context value (%d)\n", context); return; } ctxt_p = dev_p->context[context]; /* * if we have queued too much data, drop future data. */ cy_as_hal_assert(ctxt_p->queue_index * sizeof(uint16_t) + sizeof(data) <= sizeof(ctxt_p->data_queue)); for (i = 0; i < 4; i++) ctxt_p->data_queue[ctxt_p->queue_index++] = data[i]; cy_as_hal_assert((ctxt_p->queue_index % 4) == 0); dev_p->ll_queued_data = cy_true; } void cy_as_mail_box_process_data(cy_as_device *dev_p, uint16_t *data) { cy_as_context *ctxt_p; uint8_t context; uint16_t *len_p; cy_as_ll_request_response *rec_p; uint8_t st; uint16_t src, dest; context = cy_as_mbox_get_context(data[0]); if (context >= CY_RQT_CONTEXT_COUNT) { cy_as_hal_print_message("mailbox request/response received " "with invalid context value (%d)\n", context); return; } ctxt_p = dev_p->context[context]; if (cy_as_mbox_is_request(data[0])) { #if defined(CONFIG_MACH_U1_NA_SPR) || defined(CONFIG_MACH_U1_NA_USCC) if (ctxt_p->req_p == 0) { cy_as_hal_print_message(KERN_ERR "mailbox process : request pointer NULL\n"); return; } #else cy_as_hal_assert(ctxt_p->req_p != 0); #endif rec_p = ctxt_p->req_p; len_p = &ctxt_p->request_length; } else { if (ctxt_p->request_queue_p == 0 || cy_as_request_get_node_state(ctxt_p->request_queue_p) != CY_AS_REQUEST_LIST_STATE_WAITING) { cy_as_hal_print_message("mailbox response received on " "context that was not expecting a response\n"); cy_as_hal_print_message(" context: %d\n", context); cy_as_hal_print_message(" contents: 0x%04x 0x%04x " "0x%04x 0x%04x\n", data[0], data[1], data[2], data[3]); if (ctxt_p->request_queue_p != 0) cy_as_hal_print_message(" state: 0x%02x\n", ctxt_p->request_queue_p->state); return; } /* Make sure the request has an associated response */ cy_as_hal_assert(ctxt_p->request_queue_p->resp != 0); rec_p = ctxt_p->request_queue_p->resp; len_p = &ctxt_p->request_queue_p->length; } if (rec_p->stored == 0) { /* * this is the first cycle of the response */ cy_as_ll_request_response__set_code(rec_p, cy_as_mbox_get_code(data[0])); cy_as_ll_request_response__set_context(rec_p, context); if (cy_as_mbox_is_last(data[0])) { /* This is a single cycle response */ *len_p = rec_p->length; st = 1; } else { /* Ensure that enough memory has been * reserved for the response. */ cy_as_hal_assert(rec_p->length >= data[1]); *len_p = (data[1] < rec_p->length) ? data[1] : rec_p->length; st = 2; } } else st = 1; /* Trasnfer the data from the mailboxes to the response */ while (rec_p->stored < *len_p && st < 4) rec_p->data[rec_p->stored++] = data[st++]; if (cy_as_mbox_is_last(data[0])) { /* NB: The call-back that is made below can cause the * addition of more data in this queue, thus causing * a recursive overflow of the queue. this is prevented * by removing the request entry that is currently * being passed up from the data queue. if this is done, * the queue only needs to be as long as two request * entries from west bridge. */ if ((ctxt_p->rqt_index > 0) && (ctxt_p->rqt_index <= ctxt_p->queue_index)) { dest = 0; src = ctxt_p->rqt_index; while (src < ctxt_p->queue_index) ctxt_p->data_queue[dest++] = ctxt_p->data_queue[src++]; ctxt_p->rqt_index = 0; ctxt_p->queue_index = dest; cy_as_hal_assert((ctxt_p->queue_index % 4) == 0); } if (ctxt_p->request_queue_p != 0 && rec_p == ctxt_p->request_queue_p->resp) { /* * if this is the last cycle of the response, call the * callback and reset for the next response. */ cy_as_ll_request_response *resp_p = ctxt_p->request_queue_p->resp; resp_p->length = ctxt_p->request_queue_p->length; cy_as_request_set_node_state(ctxt_p->request_queue_p, CY_AS_REQUEST_LIST_STATE_RECEIVED); cy_as_device_set_in_callback(dev_p); ctxt_p->request_queue_p->callback(dev_p, context, ctxt_p->request_queue_p->rqt, resp_p, CY_AS_ERROR_SUCCESS); cy_as_device_clear_in_callback(dev_p); cy_as_ll_remove_request_queue_head(dev_p, ctxt_p); cy_as_ll_send_next_request(dev_p, ctxt_p); } else { /* Send the request to the appropriate * module to handle */ cy_as_ll_request_response *request_p = ctxt_p->req_p; ctxt_p->req_p = 0; if (request_p == NULL) { cy_as_hal_print_message(KERN_ERR "mailbox process : request_p == NULL\n"); return; } if (ctxt_p->request_callback) { cy_as_device_set_in_callback(dev_p); ctxt_p->request_callback(dev_p, context, request_p, 0, CY_AS_ERROR_SUCCESS); cy_as_device_clear_in_callback(dev_p); } cy_as_ll_init_request(request_p, 0, context, request_p->length); ctxt_p->req_p = request_p; } } } /* * This is the handler for processing queued mailbox data */ void cy_as_mail_box_queued_data_handler(cy_as_device *dev_p) { uint16_t i; /* * if more data gets queued in between our entering this call * and the end of the iteration on all contexts; we should * continue processing the queued data. */ while (dev_p->ll_queued_data) { dev_p->ll_queued_data = cy_false; for (i = 0; i < CY_RQT_CONTEXT_COUNT; i++) { uint16_t offset; cy_as_context *ctxt_p = dev_p->context[i]; cy_as_hal_assert((ctxt_p->queue_index % 4) == 0); offset = 0; while (offset < ctxt_p->queue_index) { ctxt_p->rqt_index = offset + 4; cy_as_mail_box_process_data(dev_p, ctxt_p->data_queue + offset); offset = ctxt_p->rqt_index; } ctxt_p->queue_index = 0; } } } /* * This is the handler for the mailbox interrupt. This function reads * data from the mailbox registers until a complete request or response * is received. When a complete request is received, the callback * associated with requests on that context is called. When a complete * response is recevied, the callback associated with the request that * generated the response is called. */ void cy_as_mail_box_interrupt_handler(cy_as_device *dev_p) { cy_as_hal_assert(dev_p->sig == CY_AS_DEVICE_HANDLE_SIGNATURE); /* * queue the mailbox data to preserve * order for later processing. */ cy_as_ll_queue_mailbox_data(dev_p); /* * process what was queued and anything that may be pending */ cy_as_mail_box_queued_data_handler(dev_p); } cy_as_return_status_t cy_as_ll_start(cy_as_device *dev_p) { uint16_t i; if (cy_as_device_is_low_level_running(dev_p)) return CY_AS_ERROR_ALREADY_RUNNING; dev_p->ll_sending_rqt = cy_false; dev_p->ll_abort_curr_rqt = cy_false; for (i = 0; i < CY_RQT_CONTEXT_COUNT; i++) { dev_p->context[i] = (cy_as_context *) cy_as_hal_alloc(sizeof(cy_as_context)); if (dev_p->context[i] == 0) return CY_AS_ERROR_OUT_OF_MEMORY; dev_p->context[i]->number = (uint8_t)i; dev_p->context[i]->request_callback = 0; dev_p->context[i]->request_queue_p = 0; dev_p->context[i]->last_node_p = 0; dev_p->context[i]->req_p = cy_as_ll_create_request(dev_p, 0, (uint8_t)i, max_request_length[i]); dev_p->context[i]->queue_index = 0; if (!cy_as_hal_create_sleep_channel (&dev_p->context[i]->channel)) return CY_AS_ERROR_CREATE_SLEEP_CHANNEL_FAILED; } cy_as_device_set_low_level_running(dev_p); return CY_AS_ERROR_SUCCESS; } /* * Shutdown the low level communications module. This operation will * also cancel any queued low level requests. */ cy_as_return_status_t cy_as_ll_stop(cy_as_device *dev_p) { uint8_t i; cy_as_return_status_t ret = CY_AS_ERROR_SUCCESS; cy_as_context *ctxt_p; uint32_t mask; for (i = 0; i < CY_RQT_CONTEXT_COUNT; i++) { ctxt_p = dev_p->context[i]; if (!cy_as_hal_destroy_sleep_channel(&ctxt_p->channel)) return CY_AS_ERROR_DESTROY_SLEEP_CHANNEL_FAILED; /* * now, free any queued requests and assocaited responses */ while (ctxt_p->request_queue_p) { uint32_t state; cy_as_ll_request_list_node *node_p = ctxt_p->request_queue_p; /* Mark this pair as in a cancel operation */ cy_as_request_set_node_state(node_p, CY_AS_REQUEST_LIST_STATE_CANCELING); /* Tell the caller that we are canceling this request */ /* NB: The callback is responsible for destroying the * request and the response. we cannot count on the * contents of these two after calling the callback. */ node_p->callback(dev_p, i, node_p->rqt, node_p->resp, CY_AS_ERROR_CANCELED); /* Remove the pair from the queue */ mask = cy_as_hal_disable_interrupts(); ctxt_p->request_queue_p = node_p->next; cy_as_hal_enable_interrupts(mask); /* Free the list node */ state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(node_p); cy_as_hal_enable_interrupts(state); } cy_as_ll_destroy_request(dev_p, dev_p->context[i]->req_p); cy_as_hal_free(dev_p->context[i]); dev_p->context[i] = 0; } cy_as_device_set_low_level_stopped(dev_p); return ret; } void cy_as_ll_init_request(cy_as_ll_request_response *req_p, uint16_t code, uint16_t context, uint16_t length) { uint16_t totallen = sizeof(cy_as_ll_request_response) + (length - 1) * sizeof(uint16_t); cy_as_hal_mem_set(req_p, 0, totallen); req_p->length = length; cy_as_ll_request_response__set_code(req_p, code); cy_as_ll_request_response__set_context(req_p, context); cy_as_ll_request_response__set_request(req_p); } /* * Create a new request. */ cy_as_ll_request_response * cy_as_ll_create_request(cy_as_device *dev_p, uint16_t code, uint8_t context, uint16_t length) { cy_as_ll_request_response *req_p; uint32_t state; uint16_t totallen = sizeof(cy_as_ll_request_response) + (length - 1) * sizeof(uint16_t); (void)dev_p; state = cy_as_hal_disable_interrupts(); req_p = cy_as_hal_c_b_alloc(totallen); cy_as_hal_enable_interrupts(state); if (req_p) cy_as_ll_init_request(req_p, code, context, length); return req_p; } /* * Destroy a request. */ void cy_as_ll_destroy_request(cy_as_device *dev_p, cy_as_ll_request_response *req_p) { uint32_t state; (void)dev_p; (void)req_p; state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(req_p); cy_as_hal_enable_interrupts(state); } void cy_as_ll_init_response(cy_as_ll_request_response *req_p, uint16_t length) { uint16_t totallen = sizeof(cy_as_ll_request_response) + (length - 1) * sizeof(uint16_t); cy_as_hal_mem_set(req_p, 0, totallen); req_p->length = length; cy_as_ll_request_response__set_response(req_p); } /* * Create a new response */ cy_as_ll_request_response * cy_as_ll_create_response(cy_as_device *dev_p, uint16_t length) { cy_as_ll_request_response *req_p; uint32_t state; uint16_t totallen = sizeof(cy_as_ll_request_response) + (length - 1) * sizeof(uint16_t); (void)dev_p; state = cy_as_hal_disable_interrupts(); req_p = cy_as_hal_c_b_alloc(totallen); cy_as_hal_enable_interrupts(state); if (req_p) cy_as_ll_init_response(req_p, length); return req_p; } /* * Destroy the new response */ void cy_as_ll_destroy_response(cy_as_device *dev_p, cy_as_ll_request_response *req_p) { uint32_t state; (void)dev_p; (void)req_p; state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(req_p); cy_as_hal_enable_interrupts(state); } static uint16_t cy_as_read_intr_status( cy_as_device *dev_p) { uint32_t mask; cy_bool bloop = cy_true; uint16_t v = 0, last = 0xffff; /* * before determining if the mailboxes are ready for more data, * we first check the mailbox interrupt to see if we need to * receive data. this prevents a dead-lock condition that can * occur when both sides are trying to receive data. */ while (last == last) { /* * disable interrupts to be sure we don't process the mailbox * here and have the interrupt routine try to read this data * as well. */ mask = cy_as_hal_disable_interrupts(); /* * see if there is data to be read. */ v = cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_P0_INTR_REG); if ((v & CY_AS_MEM_P0_INTR_REG_MBINT) == 0) { cy_as_hal_enable_interrupts(mask); break; } /* * queue the mailbox data for later processing. * this allows the firmware to move forward and * service the requst from the P port. */ cy_as_ll_queue_mailbox_data(dev_p); /* * enable interrupts again to service mailbox * interrupts appropriately */ cy_as_hal_enable_interrupts(mask); } /* * now, all data is received */ last = cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MB_STAT) & CY_AS_MEM_P0_MCU_MBNOTRD; while (bloop) { v = cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MB_STAT) & CY_AS_MEM_P0_MCU_MBNOTRD; if (v == last) break; last = v; } return v; } /* * Send a single request or response using the mail box register. * This function does not deal with the internal queues at all, * but only sends the request or response across to the firmware */ static cy_as_return_status_t cy_as_send_one( cy_as_device *dev_p, cy_as_ll_request_response *req_p) { int i; uint16_t mb0, v; int32_t loopcount; uint32_t int_stat; #ifdef _DEBUG if (cy_as_ll_request_response__is_request(req_p)) { switch (cy_as_ll_request_response__get_context(req_p)) { case CY_RQT_GENERAL_RQT_CONTEXT: cy_as_hal_assert(req_p->length * 2 + 2 < CY_CTX_GEN_MAX_DATA_SIZE); break; case CY_RQT_RESOURCE_RQT_CONTEXT: cy_as_hal_assert(req_p->length * 2 + 2 < CY_CTX_RES_MAX_DATA_SIZE); break; case CY_RQT_STORAGE_RQT_CONTEXT: cy_as_hal_assert(req_p->length * 2 + 2 < CY_CTX_STR_MAX_DATA_SIZE); break; case CY_RQT_USB_RQT_CONTEXT: cy_as_hal_assert(req_p->length * 2 + 2 < CY_CTX_USB_MAX_DATA_SIZE); break; } } #endif /* Write the request to the mail box registers */ if (req_p->length > 3) { uint16_t length = req_p->length; int which = 0; int st = 1; dev_p->ll_sending_rqt = cy_true; while (which < length) { loopcount = cy_as_low_level_timeout_count; do { v = cy_as_read_intr_status(dev_p); } while (v && loopcount-- > 0); if (v) { cy_as_hal_print_message( ">>>>>> LOW LEVEL TIMEOUT " "%x %x %x %x\n", cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX0), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX1), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX2), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX3)); return CY_AS_ERROR_TIMEOUT; } if (dev_p->ll_abort_curr_rqt) { dev_p->ll_sending_rqt = cy_false; dev_p->ll_abort_curr_rqt = cy_false; return CY_AS_ERROR_CANCELED; } int_stat = cy_as_hal_disable_interrupts(); /* * check again whether the mailbox is free. * it is possible that an ISR came in and * wrote into the mailboxes since we last * checked the status. */ v = cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MB_STAT) & CY_AS_MEM_P0_MCU_MBNOTRD; if (v) { /* Go back to the original check since * the mailbox is not free. */ cy_as_hal_enable_interrupts(int_stat); continue; } if (which == 0) { cy_as_hal_write_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX1, length); st = 2; } else { st = 1; } while ((which < length) && (st < 4)) { cy_as_hal_write_register(dev_p->tag, cy_cast_int2U_int16 (CY_AS_MEM_MCU_MAILBOX0 + st), req_p->data[which++]); st++; } mb0 = req_p->box0; if (which == length) { dev_p->ll_sending_rqt = cy_false; mb0 |= CY_AS_REQUEST_RESPONSE_LAST_MASK; } if (dev_p->ll_abort_curr_rqt) { dev_p->ll_sending_rqt = cy_false; dev_p->ll_abort_curr_rqt = cy_false; cy_as_hal_enable_interrupts(int_stat); return CY_AS_ERROR_CANCELED; } cy_as_hal_write_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX0, mb0); /* Wait for the MBOX interrupt to be high */ cy_as_hal_sleep150(); cy_as_hal_enable_interrupts(int_stat); } } else { check_mailbox_availability: /* * wait for the mailbox registers to become available. this * should be a very quick wait as the firmware is designed * to accept requests at interrupt time and queue them for * future processing. */ loopcount = cy_as_low_level_timeout_count; do { v = cy_as_read_intr_status(dev_p); } while (v && loopcount-- > 0); if (v) { cy_as_hal_print_message( ">>>>>> LOW LEVEL TIMEOUT %x %x %x %x\n", cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX0), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX1), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX2), cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX3)); return CY_AS_ERROR_TIMEOUT; } int_stat = cy_as_hal_disable_interrupts(); /* * check again whether the mailbox is free. it is * possible that an ISR came in and wrote into the * mailboxes since we last checked the status. */ v = cy_as_hal_read_register(dev_p->tag, CY_AS_MEM_MCU_MB_STAT) & CY_AS_MEM_P0_MCU_MBNOTRD; if (v) { /* Go back to the original check * since the mailbox is not free. */ cy_as_hal_enable_interrupts(int_stat); goto check_mailbox_availability; } /* Write the data associated with the request * into the mbox registers 1 - 3 */ v = 0; for (i = req_p->length - 1; i >= 0; i--) cy_as_hal_write_register(dev_p->tag, cy_cast_int2U_int16(CY_AS_MEM_MCU_MAILBOX1 + i), req_p->data[i]); /* Write the mbox register 0 to trigger the interrupt */ cy_as_hal_write_register(dev_p->tag, CY_AS_MEM_MCU_MAILBOX0, req_p->box0 | CY_AS_REQUEST_RESPONSE_LAST_MASK); cy_as_hal_sleep150(); cy_as_hal_enable_interrupts(int_stat); } return CY_AS_ERROR_SUCCESS; } /* * This function queues a single request to be sent to the firmware. */ extern cy_as_return_status_t cy_as_ll_send_request( cy_as_device *dev_p, /* The request to send */ cy_as_ll_request_response *req, /* Storage for a reply, must be sure * it is of sufficient size */ cy_as_ll_request_response *resp, /* If true, this is a synchronous request */ cy_bool sync, /* Callback to call when reply is received */ cy_as_response_callback cb ) { cy_as_context *ctxt_p; uint16_t box0 = req->box0; uint8_t context; cy_as_return_status_t ret = CY_AS_ERROR_SUCCESS; cy_as_ll_request_list_node *node_p; uint32_t mask, state; cy_as_hal_assert(dev_p->sig == CY_AS_DEVICE_HANDLE_SIGNATURE); context = cy_as_mbox_get_context(box0); cy_as_hal_assert(context < CY_RQT_CONTEXT_COUNT); if (context < CY_RQT_CONTEXT_COUNT) ctxt_p = dev_p->context[context] ; else return CY_AS_ERROR_INVALID_PARAMETER; /* Allocate the list node */ state = cy_as_hal_disable_interrupts(); node_p = cy_as_hal_c_b_alloc(sizeof(cy_as_ll_request_list_node)); cy_as_hal_enable_interrupts(state); if (node_p == 0) return CY_AS_ERROR_OUT_OF_MEMORY; /* Initialize the list node */ node_p->callback = cb; node_p->length = 0; node_p->next = 0; node_p->resp = resp; node_p->rqt = req; node_p->state = CY_AS_REQUEST_LIST_STATE_QUEUED; if (sync) cy_as_request_node_set_sync(node_p); /* Put the request into the queue */ mask = cy_as_hal_disable_interrupts(); if (ctxt_p->request_queue_p == 0) { /* Empty queue */ ctxt_p->request_queue_p = node_p; ctxt_p->last_node_p = node_p; } else { ctxt_p->last_node_p->next = node_p; ctxt_p->last_node_p = node_p; } cy_as_hal_enable_interrupts(mask); cy_as_ll_send_next_request(dev_p, ctxt_p); if (!cy_as_device_is_in_callback(dev_p)) { mask = cy_as_hal_disable_interrupts(); cy_as_mail_box_queued_data_handler(dev_p); cy_as_hal_enable_interrupts(mask); } return ret; } static void cy_as_ll_send_callback( cy_as_device *dev_p, uint8_t context, cy_as_ll_request_response *rqt, cy_as_ll_request_response *resp, cy_as_return_status_t ret) { (void)rqt; (void)resp; (void)ret; cy_as_hal_assert(dev_p->sig == CY_AS_DEVICE_HANDLE_SIGNATURE); /* * storage the state to return to the caller */ dev_p->ll_error = ret; /* * now wake the caller */ cy_as_hal_wake(&dev_p->context[context]->channel); } cy_as_return_status_t cy_as_ll_send_request_wait_reply( cy_as_device *dev_p, /* The request to send */ cy_as_ll_request_response *req, /* Storage for a reply, must be * sure it is of sufficient size */ cy_as_ll_request_response *resp ) { cy_as_return_status_t ret; uint8_t context; /* Larger 8 sec time-out to handle the init * delay for slower storage devices in USB FS. */ #if defined(CONFIG_MACH_U1_NA_SPR) || defined(CONFIG_MACH_U1_NA_USCC) uint32_t loopcount = 400 ; #else uint32_t loopcount = 800; #endif cy_as_context *ctxt_p; /* Get the context for the request */ context = cy_as_ll_request_response__get_context(req); cy_as_hal_assert(context < CY_RQT_CONTEXT_COUNT); if (context < CY_RQT_CONTEXT_COUNT) ctxt_p = dev_p->context[context] ; else return CY_AS_ERROR_INVALID_PARAMETER; ret = cy_as_ll_send_request(dev_p, req, resp, cy_true, cy_as_ll_send_callback); if (ret != CY_AS_ERROR_SUCCESS) return ret; while (loopcount-- > 0) { /* * sleep while we wait on the response. receiving the reply will * wake this thread. we will wait, at most 2 seconds (10 ms*200 * tries) before we timeout. note if the reply arrives, we will * not sleep the entire 10 ms, just til the reply arrives. */ cy_as_hal_sleep_on(&ctxt_p->channel, 10); /* * if the request has left the queue, it means the request has * been sent and the reply has been received. this means we can * return to the caller and be sure the reply has been received. */ if (!cy_as_ll_is_in_queue(ctxt_p, req)) return dev_p->ll_error; } /* Remove the QueueListNode for this request. */ cy_as_ll_remove_request(dev_p, ctxt_p, req, cy_true); return CY_AS_ERROR_TIMEOUT; } cy_as_return_status_t cy_as_ll_register_request_callback( cy_as_device *dev_p, uint8_t context, cy_as_response_callback cb) { cy_as_context *ctxt_p; cy_as_hal_assert(context < CY_RQT_CONTEXT_COUNT); if (context < CY_RQT_CONTEXT_COUNT) ctxt_p = dev_p->context[context] ; else return CY_AS_ERROR_INVALID_PARAMETER; ctxt_p->request_callback = cb; return CY_AS_ERROR_SUCCESS; } void cy_as_ll_request_response__pack( cy_as_ll_request_response *req_p, uint32_t offset, uint32_t length, void *data_p) { uint16_t dt; uint8_t *dp = (uint8_t *)data_p; while (length > 1) { dt = ((*dp++) << 8); dt |= (*dp++); cy_as_ll_request_response__set_word(req_p, offset, dt); offset++; length -= 2; } if (length == 1) { dt = (*dp << 8); cy_as_ll_request_response__set_word(req_p, offset, dt); } } void cy_as_ll_request_response__unpack( cy_as_ll_request_response *req_p, uint32_t offset, uint32_t length, void *data_p) { uint8_t *dp = (uint8_t *)data_p; while (length-- > 0) { uint16_t val = cy_as_ll_request_response__get_word (req_p, offset++); *dp++ = (uint8_t)((val >> 8) & 0xff); if (length) { length--; *dp++ = (uint8_t)(val & 0xff); } } } extern cy_as_return_status_t cy_as_ll_send_status_response( cy_as_device *dev_p, uint8_t context, uint16_t code, uint8_t clear_storage) { cy_as_return_status_t ret; cy_as_ll_request_response resp; cy_as_ll_request_response *resp_p = &resp; cy_as_hal_mem_set(resp_p, 0, sizeof(resp)); resp_p->length = 1; cy_as_ll_request_response__set_response(resp_p); cy_as_ll_request_response__set_context(resp_p, context); if (clear_storage) cy_as_ll_request_response__set_clear_storage_flag(resp_p); cy_as_ll_request_response__set_code(resp_p, CY_RESP_SUCCESS_FAILURE); cy_as_ll_request_response__set_word(resp_p, 0, code); ret = cy_as_send_one(dev_p, resp_p); return ret; } extern cy_as_return_status_t cy_as_ll_send_data_response( cy_as_device *dev_p, uint8_t context, uint16_t code, uint16_t length, void *data) { cy_as_ll_request_response *resp_p; uint16_t wlen; uint8_t respbuf[256]; if (length > 192) return CY_AS_ERROR_INVALID_SIZE; /* Word length for bytes */ wlen = length / 2; /* If byte length odd, add one more */ if (length % 2) wlen++; /* One for the length of field */ wlen++; resp_p = (cy_as_ll_request_response *)respbuf; cy_as_hal_mem_set(resp_p, 0, sizeof(respbuf)); resp_p->length = wlen; cy_as_ll_request_response__set_context(resp_p, context); cy_as_ll_request_response__set_code(resp_p, code); cy_as_ll_request_response__set_word(resp_p, 0, length); cy_as_ll_request_response__pack(resp_p, 1, length, data); return cy_as_send_one(dev_p, resp_p); } static cy_bool cy_as_ll_is_e_p_transfer_related_request(cy_as_ll_request_response *rqt_p, cy_as_end_point_number_t ep) { uint16_t v; uint8_t type = cy_as_ll_request_response__get_code(rqt_p); if (cy_as_ll_request_response__get_context(rqt_p) != CY_RQT_USB_RQT_CONTEXT) return cy_false; /* * when cancelling outstanding EP0 data transfers, any pending * setup ACK requests also need to be cancelled. */ if ((ep == 0) && (type == CY_RQT_ACK_SETUP_PACKET)) return cy_true; if (type != CY_RQT_USB_EP_DATA) return cy_false; v = cy_as_ll_request_response__get_word(rqt_p, 0); if ((cy_as_end_point_number_t)((v >> 13) & 1) != ep) return cy_false; return cy_true; } cy_as_return_status_t cy_as_ll_remove_ep_data_requests(cy_as_device *dev_p, cy_as_end_point_number_t ep) { cy_as_context *ctxt_p; cy_as_ll_request_list_node *node_p; uint32_t imask; /* * first, remove any queued requests */ ctxt_p = dev_p->context[CY_RQT_USB_RQT_CONTEXT]; if (ctxt_p) { for (node_p = ctxt_p->request_queue_p; node_p; node_p = node_p->next) { if (cy_as_ll_is_e_p_transfer_related_request (node_p->rqt, ep)) { cy_as_ll_remove_request(dev_p, ctxt_p, node_p->rqt, cy_false); break; } } /* * now, deal with any request that may be in transit */ imask = cy_as_hal_disable_interrupts(); if (ctxt_p->request_queue_p != 0 && cy_as_ll_is_e_p_transfer_related_request (ctxt_p->request_queue_p->rqt, ep) && cy_as_request_get_node_state(ctxt_p->request_queue_p) == CY_AS_REQUEST_LIST_STATE_WAITING) { cy_as_hal_print_message("need to remove an in-transit " "request to antioch\n"); /* * if the request has not been fully sent to west bridge * yet, abort sending. otherwise, terminate the request * with a CANCELED status. firmware will already have * terminated this transfer. */ if (dev_p->ll_sending_rqt) dev_p->ll_abort_curr_rqt = cy_true; else { uint32_t state; node_p = ctxt_p->request_queue_p; if (node_p->callback) node_p->callback(dev_p, ctxt_p->number, node_p->rqt, node_p->resp, CY_AS_ERROR_CANCELED); ctxt_p->request_queue_p = node_p->next; state = cy_as_hal_disable_interrupts(); cy_as_hal_c_b_free(node_p); cy_as_hal_enable_interrupts(state); } } cy_as_hal_enable_interrupts(imask); } return CY_AS_ERROR_SUCCESS; }