| /* 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])) { |
| cy_as_hal_assert(ctxt_p->req_p != 0); |
| 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 (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); |
| ctxt_p = dev_p->context[context]; |
| |
| /* 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. */ |
| uint32_t loopcount = 800; |
| 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); |
| ctxt_p = dev_p->context[context]; |
| |
| 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); |
| ctxt_p = dev_p->context[context]; |
| |
| 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; |
| } |