diff options
author | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-28 09:44:56 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-28 09:44:56 -0700 |
commit | e4c5bf8e3dca827a1b3a6fac494eae8c74b7e1e7 (patch) | |
tree | ea51b391f7d74ca695dcb9f5e46eb02688a92ed9 /drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c | |
parent | 81280572ca6f54009edfa4deee563e8678784218 (diff) | |
parent | a4ac0d847af9dd34d5953a5e264400326144b6b2 (diff) | |
download | op-kernel-dev-e4c5bf8e3dca827a1b3a6fac494eae8c74b7e1e7.zip op-kernel-dev-e4c5bf8e3dca827a1b3a6fac494eae8c74b7e1e7.tar.gz |
Merge 'staging-next' to Linus's tree
This merges the staging-next tree to Linus's tree and resolves
some conflicts that were present due to changes in other trees that were
affected by files here.
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c')
-rw-r--r-- | drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c b/drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c new file mode 100644 index 0000000..d43dd85 --- /dev/null +++ b/drivers/staging/westbridge/astoria/api/src/cyaslowlevel.c @@ -0,0 +1,1264 @@ +/* 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 reponse 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; +} |