| /* |
| * File - hsi_protocol_if_cmd.c |
| * |
| * Implements HSI protocol for Infineon Modem. |
| * |
| * Copyright (C) 2011 Samsung Electronics. All rights reserved. |
| * |
| * Author: Rupesh Gujare <rupesh.g@samsung.com> |
| * |
| * 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 <linux/errno.h> |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/wait.h> |
| #include <linux/sched.h> |
| |
| #include <linux/hsi_driver_if.h> |
| #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"); |
| } |
| |
| } |