aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/shost/shost_transferchecker.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/shost/shost_transferchecker.c')
-rw-r--r--drivers/usb/host/shost/shost_transferchecker.c503
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);
+}
+
+