/* * File - hsi_protocol_if_cmd.c * * Implements HSI protocol for Infineon Modem. * * Copyright (C) 2011 Samsung Electronics. All rights reserved. * * Author: Rupesh Gujare * * This package is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include "hsi-protocol-if.h" extern struct if_hsi_iface hsi_protocol_iface; extern wait_queue_head_t ipc_read_wait, ipc_write_wait; int if_hsi_openchannel(struct if_hsi_channel *channel); int if_hsi_closechannel(struct if_hsi_channel *channel); extern struct if_hsi_cmd hsi_cmd_history; extern int tx_cmd_history_p; extern int rx_cmd_history_p; /*Decode command from received PDU on channle 0*/ int hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch, u32 *param) { int ret = 0; u32 data = *cmd_data; u8 lrc_cal, lrc_act; u8 val1, val2, val3; *cmd = ((data & 0xF0000000) >> 28); switch (*cmd) { case HSI_LL_MSG_BREAK: pr_err("Command MSG_BREAK Received.\n"); break; case HSI_LL_MSG_OPEN_CONN: *ch = ((data & 0x0F000000) >> 24); *param = ((data & 0x00FFFF00) >> 8); /*Check LRC*/ val1 = ((data & 0xFF000000) >> 24); val2 = ((data & 0x00FF0000) >> 16); val3 = ((data & 0x0000FF00) >> 8); lrc_act = (data & 0x000000FF); lrc_cal = val1 ^ val2 ^ val3; if (lrc_cal != lrc_act) ret = -1; break; case HSI_LL_MSG_CONN_READY: case HSI_LL_MSG_CONN_CLOSED: case HSI_LL_MSG_CANCEL_CONN: case HSI_LL_MSG_NAK: *ch = ((data & 0x0F000000) >> 24); break; case HSI_LL_MSG_ACK: *ch = ((data & 0x0F000000) >> 24); *param = (data & 0x00FFFFFF); //printk(KERN_INFO "ACK Received ch=%d, param=%d\n",*ch, *param); break; case HSI_LL_MSG_CONF_RATE: *ch = ((data & 0x0F000000) >> 24); *param = ((data & 0x0F000000) >> 24); break; case HSI_LL_MSG_OPEN_CONN_OCTET: *ch = ((data & 0x0F000000) >> 24); *param = (data & 0x00FFFFFF); break; case HSI_LL_MSG_ECHO: case HSI_LL_MSG_INFO_REQ: case HSI_LL_MSG_INFO: case HSI_LL_MSG_CONFIGURE: case HSI_LL_MSG_ALLOCATE_CH: case HSI_LL_MSG_RELEASE_CH: case HSI_LL_MSG_INVALID: *cmd = HSI_LL_MSG_INVALID; *ch = HSI_LL_INVALID_CHANNEL; ret = -1; break; } return ret; } int protocol_create_cmd(int cmd_type, unsigned int channel, void *arg) { unsigned int command = 0; int ret = 0; switch (cmd_type) { case HSI_LL_MSG_BREAK: { command = 0; } break; case HSI_LL_MSG_OPEN_CONN: { unsigned int size = *(unsigned int *)arg; unsigned int lcr = 0; /* if(size > 4) size = (size & 0x3) ? ((size >> 2) + 1):(size >> 2); else size = 1;*/ command = ((HSI_LL_MSG_OPEN_CONN & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24) | ((size & 0x0000FFFF) << 8); lcr = ((command & 0xFF000000) >> 24) ^ ((command & 0x00FF0000) >> 16) ^ ((command & 0x0000FF00) >> 8); command = command | (lcr & 0x000000FF); } break; case HSI_LL_MSG_CONN_READY: { command = ((HSI_LL_MSG_CONN_READY & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24); } break; case HSI_LL_MSG_CONN_CLOSED: { command = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24); } break; case HSI_LL_MSG_CANCEL_CONN: { unsigned int role = *(unsigned int *)arg; command = ((HSI_LL_MSG_CANCEL_CONN & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24) | ((role & 0x000000FF) << 16); } break; case HSI_LL_MSG_ACK: { unsigned int echo_params = *(unsigned int *)arg; command = ((HSI_LL_MSG_ACK & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24) | ((echo_params & 0x00FFFFFF)); } break; case HSI_LL_MSG_NAK: { command = ((HSI_LL_MSG_NAK & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24); } break; case HSI_LL_MSG_CONF_RATE: { unsigned int baud_rate = *(unsigned int *)arg; command = ((HSI_LL_MSG_CONF_RATE & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24) | ((baud_rate & 0x00FFFFFF)); } break; case HSI_LL_MSG_OPEN_CONN_OCTET: { unsigned int size = *(unsigned int *)arg; command = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) << 28) | ((channel & 0x000000FF) << 24) | ((size & 0x00FFFFFF)); } break; case HSI_LL_MSG_ECHO: case HSI_LL_MSG_INFO_REQ: case HSI_LL_MSG_INFO: case HSI_LL_MSG_CONFIGURE: case HSI_LL_MSG_ALLOCATE_CH: case HSI_LL_MSG_RELEASE_CH: case HSI_LL_MSG_INVALID: ret = -1; break; } return command; } int set_tx_config(struct if_hsi_channel *ch, u32 mode, u32 max_channels) { struct hst_ctx tx_config; int ret; hsi_ioctl(ch->dev, HSI_IOCTL_GET_TX, &tx_config); tx_config.mode = mode; tx_config.channels = max_channels; ret = hsi_ioctl(ch->dev, HSI_IOCTL_SET_TX, &tx_config); return ret; } static int saved_cmd_queue = 0; static u32 cmd_saved[5]; int hsi_protocol_send_command(u32 cmd, u32 channel, u32 param) { struct if_hsi_channel *channel_zero; u32 cmd_array[4] = {0x00000000, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC}, ret = -1; channel_zero = &hsi_protocol_iface.channels[0]; cmd_array[0] = protocol_create_cmd(cmd, channel, ¶m); pr_debug("[%s] CMD = %08x\n",__func__, cmd_array[0]); while (channel_zero->tx_state != HSI_LL_TX_STATE_IDLE) { cmd_saved[saved_cmd_queue] = cmd_array[0]; saved_cmd_queue++; pr_debug("(%s) cmd_saved : %x(%d)\n", __func__, cmd_array[0], saved_cmd_queue); return 0; } send_retry: channel_zero->tx_state = HSI_LL_TX_STATE_TX; // For es 2.1 ver. ret = hsi_proto_write(0, cmd_array, 4); if (ret < 0) { pr_err("(%s) Command Write failed, CMD->%X\n", __func__, cmd_array[0]); channel_zero->tx_state = HSI_LL_TX_STATE_IDLE; return -1; } else { channel_zero->tx_state = HSI_LL_TX_STATE_IDLE; pr_debug("[%s] CMD = %08x\n", __func__, cmd_array[0]); hsi_cmd_history.tx_cmd[tx_cmd_history_p] = cmd_array[0]; hsi_cmd_history.tx_cmd_time[tx_cmd_history_p] = CURRENT_TIME; tx_cmd_history_p++; if (tx_cmd_history_p >= 50) tx_cmd_history_p = 0; if (saved_cmd_queue) { saved_cmd_queue--; cmd_array[0] = cmd_saved[saved_cmd_queue]; goto send_retry; } return 0; } } void rx_stm(u32 cmd, u32 ch, u32 param) { struct if_hsi_channel *channel; u32 size = 0, tmp_cmd = 0, ret, i; channel = &hsi_protocol_iface.channels[ch]; switch (cmd) { case HSI_LL_MSG_OPEN_CONN: pr_err("ERROR... OPEN_CONN Not supported. Should use OPEN_CONN_OCTECT instead.\n"); break; case HSI_LL_MSG_ECHO: pr_err("ERROR... HSI_LL_MSG_ECHO not supported.\n"); break; case HSI_LL_MSG_CONN_CLOSED: switch (channel->tx_state) { case HSI_LL_TX_STATE_WAIT_FOR_CONN_CLOSED: channel->tx_state = HSI_LL_TX_STATE_IDLE; /* ACWAKE ->LOW */ ret = hsi_ioctl(hsi_protocol_iface.channels[0].dev, HSI_IOCTL_ACWAKE_DOWN, NULL); if (ret == 0) pr_debug("ACWAKE pulled low in %s()\n", __func__); else pr_err("ACWAKE pulled low in %s() ERROR : %d\n", __func__, ret); pr_debug("[%s] Received CONN_CLOSED. ch-> %d\n", __func__,ch); break; default: pr_err("Wrong STATE for CONN_CLOSED\n"); } break; case HSI_LL_MSG_CANCEL_CONN: pr_debug("Received CANCEL_CONN\n"); break; case HSI_LL_MSG_ACK: switch (channel->tx_state) { case HSI_LL_TX_STATE_WAIT_FOR_ACK: case HSI_LL_TX_STATE_SEND_OPEN_CONN: //printk(KERN_INFO "ACK received %s()\n",__func__); channel->tx_state = HSI_LL_TX_STATE_TX; size = param; #if 0 // TEMP: send/read by 16 byte unit for v.11A(CP) if ((size > 16) && (size % 16)) size += (16 - (size % 16)); else if (size < 16) size = 16; #endif // For es 2.1 ver. if (size % 4) size += (4 - (size % 4)); pr_debug("Writing %d bytes data on channel %d, tx_buf = %x, in %s()\n", size, ch, channel->tx_buf, __func__); ret = hsi_proto_write(ch, channel->tx_buf, size); channel->tx_state = HSI_LL_TX_STATE_WAIT_FOR_CONN_CLOSED; wake_up_interruptible(&ipc_write_wait); channel->tx_nak_count = 0; break; case HSI_LL_TX_STATE_CLOSED:/* ACK as response to CANCEL_CONN */ if (channel->rx_state == HSI_LL_RX_STATE_WAIT_FOR_CANCEL_CONN_ACK) channel->rx_state = HSI_LL_RX_STATE_IDLE; break; case HSI_LL_TX_STATE_WAIT_FOR_CONF_ACK: /* ACK as response to CONF_RATE */ //TODO: SET CONF RATE pr_debug("ACK Received for CONF_RATE\n"); break; default: pr_err("ACK Received for Unknown state\n"); } break; case HSI_LL_MSG_NAK: switch (channel->tx_state) { case HSI_LL_TX_STATE_WAIT_FOR_ACK: printk(KERN_INFO "(%s) NAK received. ch->%d\n", __func__, ch); //channel->tx_state = HSI_LL_TX_STATE_NACK; if (channel->tx_nak_count < 10) { msleep(10); tmp_cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) << 28) | ((ch & 0x000000FF) << 24); for (i = 49; i >= 0; i--) { if ((hsi_cmd_history.tx_cmd[i] & 0xFFF00000) == tmp_cmd) break; } size = (hsi_cmd_history.tx_cmd[i] & 0x000FFFFF); pr_debug("(%s) Re Send OPEN CONN ch->%d, size->%d, count->%d\n", __func__, ch, size, channel->tx_nak_count); hsi_protocol_send_command(HSI_LL_MSG_OPEN_CONN_OCTET, ch, size); channel->tx_nak_count++; } else { hsi_protocol_send_command(HSI_LL_MSG_BREAK, ch, size); pr_debug("(%s) Sending MSG_BREAK. ch->%d\n", __func__, ch); //TODO Reset All channels and inform IPC write about failure (Possibly by sending signal) } break; case HSI_LL_TX_STATE_WAIT_FOR_CONF_ACK: /* NAK as response to CONF_RATE */ channel->tx_state = HSI_LL_TX_STATE_IDLE; break; default: pr_err("ERROR - Received NAK in invalid state. state->%d\n", channel->tx_state); } break; case HSI_LL_MSG_CONF_RATE: //TODO: Set Conf Rate pr_debug("CONF_RATE Received\n"); break; case HSI_LL_MSG_OPEN_CONN_OCTET: switch (channel->rx_state) { /* case HSI_LL_RX_STATE_CLOSED: */ case HSI_LL_RX_STATE_IDLE: pr_debug("OPEN_CONN_OCTET in %s(), ch-> %d\n", __func__, ch); channel->rx_state = HSI_LL_RX_STATE_TO_ACK; hsi_protocol_send_command(HSI_LL_MSG_ACK, ch, param); channel->rx_count = param; channel->rx_state = HSI_LL_RX_STATE_RX; wake_up_interruptible(&ipc_read_wait); break; case HSI_LL_RX_STATE_BLOCKED: /* TODO */ break; default: pr_err("OPEN_CONN_OCTET in invalid state, Current State -> %d\n", channel->rx_state); pr_info("Sending NAK to channel-> %d\n", ch); hsi_protocol_send_command(HSI_LL_MSG_NAK, ch, param); } break; default: pr_err("Invalid Command encountered in rx_state()\n"); } }