diff options
Diffstat (limited to 'drivers/usb/host/shost/shost_transferchecker.c')
-rw-r--r-- | drivers/usb/host/shost/shost_transferchecker.c | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/drivers/usb/host/shost/shost_transferchecker.c b/drivers/usb/host/shost/shost_transferchecker.c new file mode 100644 index 0000000..855164c --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker.c @@ -0,0 +1,503 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : CommonTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of CommonTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/01/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of CommonTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include "shost.h" + +/** + * void mask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Mask specific channel interrupt + * + * @param [IN] chnum : channel number for masking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void mask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + clear_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * void unmask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Unmask specific channel interrupt + * + * @param [IN] chnum : channel number for unmasking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void unmask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + update_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * int get_ch_info(struct hc_info * hc_reg, u8 ch_num) + * + * @brief Get current channel information about specific channel + * + * @param [OUT] hc_reg : structure to write channel inforamtion value + * [IN] ch_num : channel number for unmasking + * + * @return None + * + * @remark + * + */ +static int get_ch_info(struct hc_info *hc_reg, u8 ch_num) +{ + if (hc_reg != NULL) { + hc_reg->hc_int_msk.d32 = read_reg_32(HCINTMSK(ch_num)); + hc_reg->hc_int.d32 = read_reg_32(HCINT(ch_num)); + hc_reg->dma_addr = read_reg_32(HCDMA(ch_num)); + hc_reg->hc_char.d32 = read_reg_32(HCCHAR(ch_num)); + hc_reg->hc_size.d32 = read_reg_32(HCTSIZ(ch_num)); + + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +/** + * void get_intr_ch(u32* haint, u32* haintmsk) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [OUT] haint : HAINT register value + * [OUT] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void get_intr_ch(u32 *haint, u32 *haintmsk) +{ + *haint = read_reg_32(HAINT); + *haintmsk = read_reg_32(HAINTMSK); +} + +/** + * void clear_ch_intr(u8 ch_num, u32 clear_bit) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [IN] haint : HAINT register value + * [IN] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void clear_ch_intr(u8 ch_num, u32 clear_bit) +{ + update_reg_32(HCINT(ch_num), clear_bit); +} + + +static int +release_trans_resource(struct sec_otghost *otghost, + struct td *done_td) +{ + /* remove the pDeallocateTD from parent_ed_p. */ + otg_list_pop(&done_td->td_list_entry); + done_td->parent_ed_p->num_td--; + + /* Call deallocate to release the channel + and bandwidth resource of S3CScheduler. */ + deallocate(done_td); + delete_td(otghost, done_td); + return USB_ERR_SUCCESS; +} + +static u32 calc_transferred_size(bool f_is_complete, + struct td *td, struct hc_info *hc_info) +{ + if (f_is_complete) { + if (td->parent_ed_p->ed_desc.is_ep_in) { + return td->cur_stransfer.buf_size - + hc_info->hc_size.b.xfersize; + + } else + return td->cur_stransfer.buf_size; + + } else { + return (td->cur_stransfer.packet_cnt - + hc_info->hc_size.b.pktcnt)*td-> + parent_ed_p->ed_desc.max_packet_size; + } +} + +static void update_frame_number(struct td *pResultTD) +{ + u32 cur_frame_num = 0; + + if (pResultTD->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER || + pResultTD->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER) { + return; + } + + pResultTD->parent_ed_p->ed_desc.sched_frame += + pResultTD->parent_ed_p->ed_desc.interval; + pResultTD->parent_ed_p->ed_desc.sched_frame &= + HFNUM_MAX_FRNUM; + + cur_frame_num = oci_get_frame_num(); + + if (((cur_frame_num - pResultTD->parent_ed_p->ed_desc.sched_frame) + &HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1)) { + pResultTD->parent_ed_p->ed_desc.sched_frame = cur_frame_num; + } +} + +static void update_data_toggle(struct td *td, u8 toggle) +{ + switch (td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + if (td->standard_dev_req_info.conrol_transfer_stage + == DATA_STAGE) { + td->parent_ed_p->ed_status. + control_data_toggle.data = toggle; + } + break; + case BULK_TRANSFER: + case INT_TRANSFER: + td->parent_ed_p->ed_status.data_toggle = toggle; + break; + + case ISOCH_TRANSFER: + break; + default: + break; + } +} + +/******************************************************************************/ +/*! + * @name void update_perio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by oci. the STransfer of parent_td is for Periodic Transfer. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/******************************************************************************/ +static void update_perio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + case INT_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE : parent_td->buf_size; + break; + + case ISOCH_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.buf_size = + (parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position) > + MAX_CH_TRANSFER_SIZE + ? MAX_CH_TRANSFER_SIZE + : parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position; + + break; + + default: + break; + } +} + +/****************************************************************************/ +/*! + * @name void update_nonperio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by S3COCI. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/****************************************************************************/ +static void update_nonperio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + + case BULK_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td->transferred_szie) + > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + break; + + case CONTROL_TRANSFER: + if (parent_td->standard_dev_req_info. + conrol_transfer_stage == SETUP_STAGE) { + /* but, this case will not be occured...... */ + parent_td->cur_stransfer.phy_addr = + parent_td->standard_dev_req_info. + phy_standard_dev_req_addr; + parent_td->cur_stransfer.vir_addr = + parent_td->standard_dev_req_info. + vir_standard_dev_req_addr; + parent_td->cur_stransfer.buf_size = 8; + + } else if (parent_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td-> + transferred_szie) > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + + } else { + parent_td->cur_stransfer.phy_addr = 0; + parent_td->cur_stransfer.vir_addr = 0; + parent_td->cur_stransfer.buf_size = 0; + } + break; + default: + break; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + +} + +#include "shost_transferchecker_control.c" +#include "shost_transferchecker_interrupt.c" +#include "shost_transferchecker_bulk.c" + +/******************************************************************************/ +/*! + * @name void do_transfer_checker(struct sec_otghost *otghost) + * + * @brief this function processes the result of USB Transfer. + * So, do_transfer_checker fistly + * check which channel occurs OTG Interrupt and gets the status + * information of the channel. + * do_transfer_checker requests the information of td to scheduler. + * To process the interrupt of the channel, do_transfer_checker + * calls the sub-modules of + * S3CDoneTransferChecker, for example, + * ControlTransferChecker, BulkTransferChecker. + * according to the process result of the channel interrupt, + * do_transfer_checker decides + * the USB Transfer will be done or retransmitted. + * + * + * @param void + * + * @return void + */ +/*****************************************************************************/ +void do_transfer_checker(struct sec_otghost *otghost) +__releases(&otghost->lock) +__acquires(&otghost->lock) +{ + u32 hc_intr = 0; + u32 hc_intr_msk = 0; + u8 do_try_cnt = 0; + + struct hc_info ch_info; + struct td *done_td = {0}; + + u32 td_addr = 0; + u8 proc_result = 0; + + otg_mem_set((void *)&ch_info, 0, sizeof(struct hc_info)); + + /* Get value of HAINT... */ + get_intr_ch(&hc_intr, &hc_intr_msk); + +start_do_transfer_checker: + + while (do_try_cnt < MAX_CH_NUMBER) { + /* checks the channel number to be masked or not. */ + if (!(hc_intr & hc_intr_msk & (1 << do_try_cnt))) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the address of the struct td + to have the channel to be interrupted. */ + if (!(get_td_info(do_try_cnt, &td_addr))) { + + done_td = (struct td *)td_addr; + + if (do_try_cnt != done_td->cur_stransfer.alloc_chnum) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + } else { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the informationof channel to be interrupted. */ + get_ch_info(&ch_info, do_try_cnt); + + switch (done_td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + proc_result = + process_control_transfer(done_td, &ch_info); + break; + case BULK_TRANSFER: + proc_result = process_bulk_transfer(done_td, &ch_info); + break; + case INT_TRANSFER: + proc_result = process_intr_transfer(done_td, &ch_info); + break; + case ISOCH_TRANSFER: + /* proc_result = + ProcessIsochTransfer(done_td, &ch_info); */ + break; + default: + break; + } + + if ((proc_result == RE_TRANSMIT) || + (proc_result == RE_SCHEDULE)) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->is_transfer_done = false; + done_td->is_transferring = false; + + if (done_td->parent_ed_p->ed_desc.endpoint_type + == CONTROL_TRANSFER || + done_td->parent_ed_p->ed_desc.endpoint_type + == BULK_TRANSFER) { + + update_nonperio_stransfer(done_td); + + } else + update_perio_stransfer(done_td); + + if (proc_result == RE_TRANSMIT) + retransmit(otghost, done_td); + else + reschedule(done_td); + } + + else if (proc_result == DE_ALLOCATE) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->parent_ed_p->ed_status. + in_transferring_td = 0; + done_td->is_transfer_done = true; + done_td->is_transferring = false; + + spin_unlock(&otghost->lock); + otg_usbcore_giveback(done_td); + spin_lock(&otghost->lock); + release_trans_resource(otghost, done_td); + + } else { /* NO_ACTION.... */ + done_td->parent_ed_p->ed_status. + is_in_transferring = true; + done_td->parent_ed_p->ed_status. + in_transferring_td = (u32)done_td; + done_td->is_transfer_done = false; + done_td->is_transferring = true; + } + do_try_cnt++; + } + /* Complete to process the Channel Interrupt. + So. we now start to scheduler of S3CScheduler. */ + do_schedule(otghost); +} + + |