899 lines
28 KiB
C
899 lines
28 KiB
C
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/delay.h>
|
|
#include "io_expander.h"
|
|
#include "transceiver.h"
|
|
|
|
|
|
/* ========== Register EEPROM address mapping ==========
|
|
*/
|
|
struct eeprom_map_s eeprom_map_qsfp = {
|
|
.addr_rx_los =0x50, .page_rx_los =-1, .offset_rx_los =3, .length_rx_los =1,
|
|
.addr_tx_disable =0x50, .page_tx_disable =-1, .offset_tx_disable =86, .length_tx_disable =1,
|
|
.addr_tx_fault =0x50, .page_tx_fault =-1, .offset_tx_fault =4, .length_tx_fault =1,
|
|
};
|
|
|
|
struct eeprom_map_s eeprom_map_qsfp28 = {
|
|
.addr_rx_los =0x50, .page_rx_los =-1, .offset_rx_los =3, .length_rx_los =1,
|
|
.addr_tx_disable =0x50, .page_tx_disable =-1, .offset_tx_disable =86, .length_tx_disable =1,
|
|
.addr_tx_fault =0x50, .page_tx_fault =-1, .offset_tx_fault =4, .length_tx_fault =1,
|
|
};
|
|
|
|
|
|
/* ========== Utility Functions ==========
|
|
*/
|
|
void
|
|
alarm_msg_2_user(struct transvr_obj_s *self,
|
|
char *emsg) {
|
|
|
|
SWPS_ERR("%s on %s.\n", emsg, self->swp_name);
|
|
}
|
|
|
|
|
|
/* ========== Private functions ==========
|
|
*/
|
|
static int
|
|
_reload_transvr_obj(struct transvr_obj_s *self,int new_type);
|
|
|
|
static int
|
|
reload_transvr_obj(struct transvr_obj_s *self,int new_type);
|
|
|
|
static int
|
|
_transvr_init_handler(struct transvr_obj_s *self);
|
|
|
|
static void
|
|
_transvr_clean_retry(struct transvr_obj_s *self) {
|
|
self->retry = 0;
|
|
}
|
|
|
|
|
|
static int
|
|
_transvr_handle_retry(struct transvr_obj_s *self, int retry) {
|
|
/* Return: 0: keep retry
|
|
* -1: stop retry
|
|
*/
|
|
if (self->retry == 0) {
|
|
self->retry = retry;
|
|
}
|
|
self->retry -= 1;
|
|
if (self->retry <= 0) {
|
|
_transvr_clean_retry(self);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_common_setup_page(struct transvr_obj_s *self,
|
|
int addr,
|
|
int page,
|
|
int offset,
|
|
int len,
|
|
int show_e) {
|
|
/* return:
|
|
* 0 : OK
|
|
* -1 : EEPROM settings incorrect
|
|
* -2 : I2C R/W failure
|
|
* -3 : Undefined case
|
|
*/
|
|
int retval = DEBUG_TRANSVR_INT_VAL;
|
|
char *emsg = DEBUG_TRANSVR_STR_VAL;
|
|
|
|
/* Check */
|
|
if ((addr < 0) || (offset < 0) || (len < 0)) {
|
|
emsg = "EEPROM settings incorrect";
|
|
retval = -1;
|
|
goto err_common_setup_page;
|
|
}
|
|
/* Case1: continue access */
|
|
if ((self->i2c_client_p->addr == addr) &&
|
|
(self->curr_page == page)) {
|
|
return 0;
|
|
}
|
|
self->i2c_client_p->addr = addr;
|
|
/* Case2: select lower page */
|
|
if (page == -1) {
|
|
self->curr_page = page;
|
|
return 0;
|
|
}
|
|
/* Case3: select upper page */
|
|
if (page >= 0) {
|
|
goto upper_common_setup_page;
|
|
}
|
|
/* Unexpected case */
|
|
show_e = 1;
|
|
emsg = "Unexpected case";
|
|
retval = -3;
|
|
goto err_common_setup_page;
|
|
|
|
upper_common_setup_page:
|
|
if (i2c_smbus_write_byte_data(self->i2c_client_p,
|
|
VAL_TRANSVR_PAGE_SELECT_OFFSET,
|
|
page) < 0) {
|
|
emsg = "I2C R/W failure";
|
|
retval = -2;
|
|
goto err_common_setup_page;
|
|
}
|
|
self->curr_page = page;
|
|
mdelay(VAL_TRANSVR_PAGE_SELECT_DELAY);
|
|
return 0;
|
|
|
|
err_common_setup_page:
|
|
if (show_e) {
|
|
SWPS_INFO("%s: %s", __func__, emsg);
|
|
SWPS_INFO("%s: <addr>:0x%02x <page>:%d <offs>:%d <len>:%d\n",
|
|
__func__, addr, page, offset, len);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/* ========== Object functions for Final State Machine ==========
|
|
*/
|
|
int
|
|
is_plugged(struct transvr_obj_s *self){
|
|
|
|
int limit = 63;
|
|
int present = DEBUG_TRANSVR_INT_VAL;
|
|
char emsg[64] = DEBUG_TRANSVR_STR_VAL;
|
|
struct ioexp_obj_s *ioexp_p = self->ioexp_obj_p;
|
|
|
|
if (!ioexp_p) {
|
|
snprintf(emsg, limit, "ioexp_p is null!");
|
|
goto err_is_plugged_1;
|
|
}
|
|
present = ioexp_p->get_present(ioexp_p, self->ioexp_virt_offset);
|
|
switch (present){
|
|
case 0:
|
|
return 1;
|
|
case 1:
|
|
return 0;
|
|
case ERR_IOEXP_UNINIT:
|
|
snprintf(emsg, limit, "ioexp_p not ready!");
|
|
goto err_is_plugged_1;
|
|
default:
|
|
if (ioexp_p->state == STATE_IOEXP_INIT){
|
|
snprintf(emsg, limit, "ioexp_p not ready!");
|
|
goto err_is_plugged_1;
|
|
}
|
|
break;
|
|
}
|
|
SWPS_INFO("%s: Exception case! <pres>:%d <istate>:%d\n",
|
|
__func__, present, ioexp_p->state);
|
|
return 0;
|
|
|
|
err_is_plugged_1:
|
|
SWPS_DEBUG("%s: %s\n", __func__, emsg);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
detect_transvr_type(struct transvr_obj_s* self){
|
|
|
|
int type = TRANSVR_TYPE_ERROR;
|
|
|
|
self->i2c_client_p->addr = VAL_TRANSVR_COMID_ARREESS;
|
|
type = i2c_smbus_read_byte_data(self->i2c_client_p,
|
|
VAL_TRANSVR_COMID_OFFSET);
|
|
|
|
/* Case: 1. Wait transceiver I2C module.
|
|
* 2. Transceiver I2C module failure.
|
|
* Note: 1. SFF allow maximum transceiver initial time is 2 second. So, there
|
|
* are exist some case that we need to wait transceiver.
|
|
* For these case, we keeps status on "TRANSVR_TYPE_UNPLUGGED", than
|
|
* state machine will keep trace with it.
|
|
* 2. There exist some I2C failure case we need to handle. Such as user
|
|
* insert the failure transceiver, or any reason cause it abnormal.
|
|
*/
|
|
if (type < 0){
|
|
switch (type) {
|
|
case -EIO:
|
|
SWPS_DEBUG("%s: %s smbus return:-5 (I/O error)\n",
|
|
__func__, self->swp_name);
|
|
return TRANSVR_TYPE_UNPLUGGED;
|
|
case -ENXIO:
|
|
SWPS_DEBUG("%s: %s smbus return:-6 (No such device or address)\n",
|
|
__func__, self->swp_name);
|
|
return TRANSVR_TYPE_UNPLUGGED;
|
|
default:
|
|
break;
|
|
}
|
|
SWPS_INFO("%s: %s unexpected smbus return:%d \n",
|
|
__func__, self->swp_name, type);
|
|
return TRANSVR_TYPE_ERROR;
|
|
}
|
|
/* Identify valid transceiver type */
|
|
switch (type){
|
|
case TRANSVR_TYPE_SFP:
|
|
case TRANSVR_TYPE_QSFP:
|
|
case TRANSVR_TYPE_QSFP_PLUS:
|
|
case TRANSVR_TYPE_QSFP_28:
|
|
break;
|
|
case TRANSVR_TYPE_UNKNOW_1:
|
|
case TRANSVR_TYPE_UNKNOW_2:
|
|
type = TRANSVR_TYPE_UNKNOW_2;
|
|
break;
|
|
default:
|
|
SWPS_DEBUG("%s: unknow type:0x%02x \n", __func__, type);
|
|
type = TRANSVR_TYPE_ERROR;
|
|
break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
|
|
static int
|
|
detect_transvr_state(struct transvr_obj_s *self,
|
|
int result[2]){
|
|
/* [return] [result-0] [result-1]
|
|
* 0 STATE_TRANSVR_CONNECTED TRANSVR_TYPE_FAKE
|
|
* 0 STATE_TRANSVR_DISCONNECTED TRANSVR_TYPE_UNPLUGGED
|
|
* 0 STATE_TRANSVR_ISOLATED TRANSVR_TYPE_ERROR
|
|
* 0 STATE_TRANSVR_INIT <NEW_TYPE>/<OLD_TYPE>
|
|
* 0 STATE_TRANSVR_SWAPPED <NEW_TYPE>
|
|
* 0 STATE_TRANSVR_CONNECTED <OLD_TYPE>
|
|
* ERR_TRNASVR_BE_ISOLATED STATE_TRANSVR_ISOLATED TRANSVR_TYPE_ERROR <Isolated>
|
|
* ERR_TRANSVR_I2C_CRASH STATE_TRANSVR_UNEXCEPTED TRANSVR_TYPE_ERROR <New event>
|
|
* ERR_TRANSVR_UNEXCPT STATE_TRANSVR_UNEXCEPTED TRANSVR_TYPE_UNKNOW_1/2
|
|
*/
|
|
result[0] = STATE_TRANSVR_UNEXCEPTED; /* For return state */
|
|
result[1] = TRANSVR_TYPE_ERROR; /* For return type */
|
|
|
|
/* Case1: Fake type */
|
|
if (self->type == TRANSVR_TYPE_FAKE){
|
|
result[0] = STATE_TRANSVR_CONNECTED;
|
|
result[1] = TRANSVR_TYPE_FAKE;
|
|
return 0;
|
|
}
|
|
/* Case2: Transceiver unplugged */
|
|
if (!is_plugged(self)){
|
|
result[0] = STATE_TRANSVR_DISCONNECTED;
|
|
result[1] = TRANSVR_TYPE_UNPLUGGED;
|
|
return 0;
|
|
}
|
|
/* Case3: Transceiver be isolated */
|
|
if (self->state == STATE_TRANSVR_ISOLATED){
|
|
result[0] = STATE_TRANSVR_ISOLATED;
|
|
result[1] = TRANSVR_TYPE_ERROR;
|
|
return ERR_TRNASVR_BE_ISOLATED;
|
|
}
|
|
/* Case4: Transceiver plugged */
|
|
result[1] = detect_transvr_type(self);
|
|
/* Case4.1: I2C topology crash
|
|
* Note : There are some I2C issues cause by transceiver/cables.
|
|
* We need to check topology status when user insert it.
|
|
* But in this step, we can't not ensure this is the issues
|
|
* port. So, it return the ERR_TRANSVR_I2C_CRASH, then upper
|
|
* layer will diagnostic I2C topology.
|
|
*/
|
|
if (check_channel_tier_1() < 0) {
|
|
SWPS_INFO("%s: %s detect I2C crash <obj-state>:%d\n",
|
|
__func__, self->swp_name, self->state);
|
|
result[0] = STATE_TRANSVR_UNEXCEPTED;
|
|
result[1] = TRANSVR_TYPE_ERROR;
|
|
return ERR_TRANSVR_I2C_CRASH;
|
|
}
|
|
/* Case4.2: System initial not ready,
|
|
* Note : Sometime i2c channel or transceiver EEPROM will delay that will
|
|
* cause system in inconsistent state between EEPROM and IOEXP.
|
|
* In this case, SWP transceiver object keep state at LINK_DOWN
|
|
* to wait system ready.
|
|
* By the way, State Machine will handle these case.
|
|
*/
|
|
if (result[1] == TRANSVR_TYPE_UNPLUGGED){
|
|
result[0] = STATE_TRANSVR_DISCONNECTED;
|
|
return 0;
|
|
}
|
|
/* Case4.3: Error transceiver type */
|
|
if (result[1] == TRANSVR_TYPE_ERROR){
|
|
result[0] = STATE_TRANSVR_ISOLATED;
|
|
SWPS_INFO("%s: %s detect error type\n", __func__, self->swp_name);
|
|
alarm_msg_2_user(self, "detected transceiver/cables not meet SFF standard!");
|
|
return ERR_TRNASVR_BE_ISOLATED;
|
|
}
|
|
/* Case3.3: Unknow transceiver type */
|
|
if ((result[1] == TRANSVR_TYPE_UNKNOW_1) ||
|
|
(result[1] == TRANSVR_TYPE_UNKNOW_2) ){
|
|
result[0] = STATE_TRANSVR_UNEXCEPTED;
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
}
|
|
/* Case3.4: During initial process */
|
|
if (self->state == STATE_TRANSVR_INIT){
|
|
result[0] = STATE_TRANSVR_INIT;
|
|
return 0;
|
|
}
|
|
/* Case3.5: Transceiver be swapped */
|
|
if (self->type != result[1]){
|
|
result[0] = STATE_TRANSVR_SWAPPED;
|
|
return 0;
|
|
}
|
|
/* Case3.6: Link up state */
|
|
result[0] = STATE_TRANSVR_CONNECTED;
|
|
return 0;
|
|
}
|
|
int
|
|
common_fsm_4_direct_mode(struct transvr_obj_s* self,
|
|
char *caller_name){
|
|
|
|
int err;
|
|
int detect_result[2];
|
|
int current_state = STATE_TRANSVR_UNEXCEPTED;
|
|
int current_type = TRANSVR_TYPE_ERROR;
|
|
|
|
if (self->state == STATE_TRANSVR_NEW) {
|
|
if (_transvr_init_handler(self) < 0){
|
|
return ERR_TRANSVR_INIT_FAIL;
|
|
}
|
|
}
|
|
err = detect_transvr_state(self, detect_result);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
/* In Direct mode, driver only detect transceiver when user call driver interface
|
|
* which on sysfs. So it only need consider the state of Transceiver.
|
|
*/
|
|
current_state = detect_result[0];
|
|
current_type = detect_result[1];
|
|
|
|
switch (current_state){
|
|
|
|
case STATE_TRANSVR_DISCONNECTED: /* Transceiver is not plugged */
|
|
self->state = current_state;
|
|
self->type = current_type;
|
|
return ERR_TRANSVR_UNPLUGGED;
|
|
|
|
case STATE_TRANSVR_INIT: /* Transceiver is plugged, system not ready */
|
|
return ERR_TRANSVR_UNINIT;
|
|
|
|
case STATE_TRANSVR_ISOLATED: /* Transceiver is plugged, but has some issues */
|
|
return ERR_TRNASVR_BE_ISOLATED;
|
|
|
|
case STATE_TRANSVR_CONNECTED: /* Transceiver is plugged, system is ready */
|
|
self->state = current_state;
|
|
self->type = current_type;
|
|
return 0;
|
|
|
|
case STATE_TRANSVR_SWAPPED: /* Transceiver is plugged, system detect user changed */
|
|
self->type = current_type;
|
|
if (reload_transvr_obj(self, current_type) < 0){
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
}
|
|
self->state = current_state;
|
|
return 0;
|
|
|
|
case STATE_TRANSVR_UNEXCEPTED: /* Transceiver type or state is unexpected case */
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
self->type = TRANSVR_TYPE_ERROR;
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
|
|
default:
|
|
SWPS_INFO("%s: state:%d not in define.\n", __func__, current_state);
|
|
break;
|
|
}
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
}
|
|
|
|
int
|
|
fake_fsm_4_direct_mode(struct transvr_obj_s* self,
|
|
char *caller_name){
|
|
self->state = STATE_TRANSVR_CONNECTED;
|
|
self->type = TRANSVR_TYPE_FAKE;
|
|
return 0;
|
|
}
|
|
|
|
/* ========== Object Initial handler ==========
|
|
*/
|
|
static int
|
|
_is_transvr_valid(struct transvr_obj_s *self,
|
|
int type,
|
|
int state) {
|
|
/* [Return]
|
|
* 0 : OK, inserted
|
|
* EVENT_TRANSVR_INIT_DOWN : OK, removed
|
|
* EVENT_TRANSVR_INIT_FAIL : Outside error, type doesn't supported
|
|
* EVENT_TRANSVR_EXCEP_INIT : Internal error, state undefined
|
|
*/
|
|
switch (type) {
|
|
case TRANSVR_TYPE_SFP:
|
|
case TRANSVR_TYPE_QSFP:
|
|
case TRANSVR_TYPE_QSFP_PLUS:
|
|
case TRANSVR_TYPE_QSFP_28:
|
|
case TRANSVR_TYPE_UNPLUGGED:
|
|
case TRANSVR_TYPE_FAKE:
|
|
break;
|
|
default:
|
|
SWPS_INFO("detect undefined type:0x%02x on %s\n",
|
|
type, self->swp_name);
|
|
return EVENT_TRANSVR_INIT_FAIL;
|
|
}
|
|
switch (state) {
|
|
case STATE_TRANSVR_DISCONNECTED:
|
|
return EVENT_TRANSVR_INIT_DOWN;
|
|
case STATE_TRANSVR_INIT:
|
|
case STATE_TRANSVR_CONNECTED:
|
|
case STATE_TRANSVR_SWAPPED:
|
|
break;
|
|
default:
|
|
SWPS_INFO("detect undefined state:%d on %s\n",
|
|
state, self->swp_name);
|
|
return EVENT_TRANSVR_EXCEP_INIT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
_is_transvr_hw_ready(struct transvr_obj_s *self,
|
|
int type){
|
|
/* [Return]
|
|
* EVENT_TRANSVR_TASK_DONE : Ready
|
|
* EVENT_TRANSVR_TASK_WAIT : Not ready
|
|
* EVENT_TRANSVR_INIT_FAIL : Error
|
|
*/
|
|
int addr = DEBUG_TRANSVR_INT_VAL;
|
|
int page = DEBUG_TRANSVR_INT_VAL;
|
|
int offs = DEBUG_TRANSVR_INT_VAL;
|
|
int bit = DEBUG_TRANSVR_INT_VAL;
|
|
int ready = DEBUG_TRANSVR_INT_VAL;
|
|
int err = DEBUG_TRANSVR_INT_VAL;
|
|
char *emsg = DEBUG_TRANSVR_STR_VAL;
|
|
uint8_t ab_val = DEBUG_TRANSVR_HEX_VAL;
|
|
|
|
switch (type) {
|
|
case TRANSVR_TYPE_SFP:
|
|
addr = VAL_TRANSVR_8472_READY_ADDR;
|
|
page = VAL_TRANSVR_8472_READY_PAGE;
|
|
offs = VAL_TRANSVR_8472_READY_OFFSET;
|
|
bit = VAL_TRANSVR_8472_READY_BIT;
|
|
ready = VAL_TRANSVR_8472_READY_VALUE;
|
|
ab_val = VAL_TRANSVR_8472_READY_ABNORMAL;
|
|
break;
|
|
|
|
case TRANSVR_TYPE_QSFP:
|
|
case TRANSVR_TYPE_QSFP_PLUS:
|
|
case TRANSVR_TYPE_QSFP_28:
|
|
addr = VAL_TRANSVR_8436_READY_ADDR;
|
|
page = VAL_TRANSVR_8436_READY_PAGE;
|
|
offs = VAL_TRANSVR_8436_READY_OFFSET;
|
|
bit = VAL_TRANSVR_8436_READY_BIT;
|
|
ready = VAL_TRANSVR_8436_READY_VALUE;
|
|
ab_val = VAL_TRANSVR_8436_READY_ABNORMAL;
|
|
break;
|
|
|
|
case TRANSVR_TYPE_UNPLUGGED:
|
|
case TRANSVR_TYPE_FAKE:
|
|
return EVENT_TRANSVR_TASK_DONE;
|
|
|
|
default:
|
|
emsg = "unexpected case";
|
|
goto err_is_transvr_hw_ready;
|
|
}
|
|
/* Select target page */
|
|
err = _common_setup_page(self, addr, page, offs, 1, 0);
|
|
if (err < 0) {
|
|
emsg = "setup page fail";
|
|
goto err_is_transvr_hw_ready;
|
|
}
|
|
/* Check feature supported
|
|
* [Note]
|
|
* Some of transceiver/cables doesn't support "Status Indicators"
|
|
* (ex:DAC, RJ45 copper SFP ...etc). In these case, we bypass the
|
|
* step of checking Status Indicators, then state machine will take
|
|
* the following handle procedure.
|
|
*/
|
|
err = i2c_smbus_read_byte_data(self->i2c_client_p,
|
|
VAL_TRANSVR_COMID_OFFSET);
|
|
if (err < 0) {
|
|
emsg = "doesn't support Status Indicators";
|
|
goto bypass_is_transvr_hw_ready;
|
|
}
|
|
/* Filter abnormal case */
|
|
if (err == ab_val) {
|
|
emsg = "detect using unusual definition.";
|
|
goto bypass_is_transvr_hw_ready;
|
|
}
|
|
/* Get Status Indicators */
|
|
err = i2c_smbus_read_byte_data(self->i2c_client_p, offs);
|
|
if (err < 0) {
|
|
emsg = "detect current value fail";
|
|
goto err_is_transvr_hw_ready;
|
|
}
|
|
if ((err & (1<<bit)) == ready) {
|
|
return EVENT_TRANSVR_TASK_DONE;
|
|
}
|
|
return EVENT_TRANSVR_TASK_WAIT;
|
|
|
|
bypass_is_transvr_hw_ready:
|
|
SWPS_DEBUG("%s: %s <type>:%d\n", __func__, emsg, type);
|
|
return EVENT_TRANSVR_TASK_DONE;
|
|
|
|
err_is_transvr_hw_ready:
|
|
SWPS_DEBUG("%s: %s <type>:%d\n", __func__, emsg, type);
|
|
return EVENT_TRANSVR_INIT_FAIL;
|
|
}
|
|
|
|
static int
|
|
_transvr_init_handler(struct transvr_obj_s *self){
|
|
|
|
int detect[2];
|
|
int d_state = STATE_TRANSVR_UNEXCEPTED;
|
|
int d_type = TRANSVR_TYPE_ERROR;
|
|
int result = ERR_TRANSVR_UNINIT;
|
|
int retry = 6; /* (6+1) x 0.3 = 2.1s > spec:2.0s */
|
|
int elimit = 63;
|
|
char emsg[64] = DEBUG_TRANSVR_STR_VAL;
|
|
|
|
/* Clean and check callback */
|
|
self->state = STATE_TRANSVR_INIT;
|
|
if (self->init == NULL) {
|
|
snprintf(emsg, elimit, "init() is null");
|
|
goto initer_err_case_unexcept_0;
|
|
}
|
|
/* Detect transceiver information */
|
|
result = detect_transvr_state(self, detect);
|
|
if (result < 0) {
|
|
snprintf(emsg, elimit, "detect_transvr_state() fail");
|
|
switch (result) {
|
|
case ERR_TRANSVR_I2C_CRASH:
|
|
goto initer_err_case_i2c_ceash;
|
|
case ERR_TRNASVR_BE_ISOLATED:
|
|
goto initer_err_case_be_isolated;
|
|
|
|
case ERR_TRANSVR_UNEXCPT:
|
|
default:
|
|
break;
|
|
}
|
|
goto initer_err_case_retry_1;
|
|
}
|
|
d_state = detect[0];
|
|
d_type = detect[1];
|
|
|
|
/* Verify transceiver type and state */
|
|
switch (_is_transvr_valid(self, d_type, d_state)) {
|
|
case 0:
|
|
break;
|
|
case EVENT_TRANSVR_INIT_DOWN:
|
|
goto initer_ok_case_down;;
|
|
case EVENT_TRANSVR_INIT_FAIL:
|
|
snprintf(emsg, elimit, "transceiver type doesn't support");
|
|
goto initer_err_case_alarm_to_user;
|
|
case EVENT_TRANSVR_EXCEP_INIT:
|
|
default:
|
|
goto initer_err_case_unexcept_0;
|
|
}
|
|
|
|
/* Handle reload case */
|
|
if (self->type != d_type){
|
|
/* This is the protect mechanism. Normally, This case will not happen.
|
|
* When State machine detect swap event during initial, It will trigger
|
|
* reload function to ensure type correct. */
|
|
if (_reload_transvr_obj(self, d_type) < 0){
|
|
snprintf(emsg, elimit, "reload object fail");
|
|
goto initer_err_case_unexcept_0;
|
|
}
|
|
}
|
|
|
|
/* Check transceiver HW initial ready */
|
|
switch (_is_transvr_hw_ready(self, d_type)) {
|
|
case EVENT_TRANSVR_TASK_DONE:
|
|
break;
|
|
case EVENT_TRANSVR_TASK_WAIT:
|
|
goto initer_err_case_retry_1;
|
|
case EVENT_TRANSVR_INIT_FAIL:
|
|
default:
|
|
goto initer_err_case_unexcept_0;
|
|
}
|
|
|
|
/* Try to update all and check */
|
|
if (self->update_all(self, 1) < 0){
|
|
/* For some transceiver, EEPROME has lag issues during initial stage.
|
|
* In this case, we set status back to STATE_TRANSVR_NEW, than it will
|
|
* be checked in next polling cycle. */
|
|
goto initer_err_case_retry_1;
|
|
}
|
|
|
|
/* Execute init() call back */
|
|
result = self->init(self);
|
|
switch (result) {
|
|
case EVENT_TRANSVR_TASK_DONE:
|
|
break;
|
|
case EVENT_TRANSVR_TASK_WAIT:
|
|
goto initer_ok_case_wait;
|
|
|
|
default:
|
|
snprintf(emsg, elimit, "undefined init() return:%d\n", result);
|
|
goto initer_err_case_unexcept_0;
|
|
}
|
|
goto initer_ok_case_up;
|
|
|
|
|
|
initer_ok_case_wait:
|
|
return EVENT_TRANSVR_TASK_WAIT;
|
|
|
|
initer_ok_case_up:
|
|
self->state = STATE_TRANSVR_CONNECTED;
|
|
self->temp = 0;
|
|
return EVENT_TRANSVR_INIT_UP;
|
|
|
|
initer_ok_case_down:
|
|
self->temp = 0;
|
|
self->state = STATE_TRANSVR_DISCONNECTED;
|
|
return EVENT_TRANSVR_INIT_DOWN;
|
|
|
|
initer_err_case_i2c_ceash:
|
|
SWPS_DEBUG("%s: %s <port>:%s <case>:I2C crash\n",
|
|
__func__, emsg, self->swp_name);
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
return EVENT_TRANSVR_I2C_CRASH;
|
|
|
|
initer_err_case_be_isolated:
|
|
SWPS_DEBUG("%s: %s <port>:%s <case>:isolated\n",
|
|
__func__, emsg, self->swp_name);
|
|
self->state = STATE_TRANSVR_ISOLATED;
|
|
return EVENT_TRANSVR_EXCEP_ISOLATED;
|
|
|
|
initer_err_case_retry_1:
|
|
SWPS_DEBUG("%s: %s <port>:%s <case>:retry\n",
|
|
__func__, emsg, self->swp_name);
|
|
if (_transvr_handle_retry(self, retry) == 0) {
|
|
self->state = STATE_TRANSVR_NEW;
|
|
return EVENT_TRANSVR_INIT_REINIT;
|
|
}
|
|
goto initer_err_case_alarm_to_user;
|
|
|
|
initer_err_case_unexcept_0:
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
return EVENT_TRANSVR_INIT_FAIL;
|
|
|
|
initer_err_case_alarm_to_user:
|
|
SWPS_DEBUG("%s: %s <port>:%s <case>:alarm_to_user\n",
|
|
__func__, emsg, self->swp_name);
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
alarm_msg_2_user(self, "detected transceiver/cables not meet SFF standard");
|
|
return EVENT_TRANSVR_INIT_FAIL;
|
|
}
|
|
|
|
static int
|
|
setup_transvr_private_cb(struct transvr_obj_s *self,
|
|
int transvr_type){
|
|
switch (transvr_type){
|
|
case TRANSVR_TYPE_SFP:
|
|
self->fsm_4_direct = common_fsm_4_direct_mode;
|
|
return 0;
|
|
|
|
case TRANSVR_TYPE_QSFP:
|
|
case TRANSVR_TYPE_QSFP_PLUS:
|
|
self->fsm_4_direct = common_fsm_4_direct_mode;
|
|
return 0;
|
|
|
|
case TRANSVR_TYPE_QSFP_28:
|
|
self->fsm_4_direct = common_fsm_4_direct_mode;
|
|
return 0;
|
|
|
|
case TRANSVR_TYPE_FAKE:
|
|
self->fsm_4_direct = fake_fsm_4_direct_mode;
|
|
return 0;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
SWPS_WARN("%s: Detect non-defined type:%d\n", __func__, transvr_type);
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
}
|
|
|
|
|
|
static struct eeprom_map_s *
|
|
get_eeprom_map(int transvr_type){
|
|
|
|
switch (transvr_type){
|
|
case TRANSVR_TYPE_QSFP:
|
|
case TRANSVR_TYPE_QSFP_PLUS:
|
|
return &eeprom_map_qsfp;
|
|
case TRANSVR_TYPE_QSFP_28:
|
|
return &eeprom_map_qsfp28;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
SWPS_WARN("%s: Detect non-defined type:%d\n", __func__, transvr_type);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
setup_transvr_ssize_attr(char *swp_name,
|
|
struct transvr_obj_s *self,
|
|
struct eeprom_map_s *map_p,
|
|
struct ioexp_obj_s *ioexp_obj_p,
|
|
int ioexp_virt_offset,
|
|
int transvr_type,
|
|
int chipset_type,
|
|
int chan_id,
|
|
int run_mode){
|
|
switch (run_mode){
|
|
case TRANSVR_MODE_DIRECT: /* Direct access device mode */
|
|
self->mode = run_mode;
|
|
break;
|
|
default:
|
|
SWPS_ERR("%s: non-defined run_mode:%d\n",
|
|
__func__, run_mode);
|
|
self->mode = DEBUG_TRANSVR_INT_VAL;
|
|
return -1;
|
|
}
|
|
self->eeprom_map_p = map_p;
|
|
self->ioexp_obj_p = ioexp_obj_p;
|
|
self->ioexp_virt_offset = ioexp_virt_offset;
|
|
self->chan_id = chan_id;
|
|
self->layout = transvr_type;
|
|
self->type = transvr_type;
|
|
self->chipset_type = chipset_type;
|
|
self->state = STATE_TRANSVR_NEW;
|
|
self->info = STATE_TRANSVR_NEW;
|
|
self->auto_tx_disable = VAL_TRANSVR_FUNCTION_DISABLE;
|
|
strncpy(self->swp_name, swp_name, 32);
|
|
mutex_init(&self->lock);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
setup_i2c_client(struct transvr_obj_s *self){
|
|
|
|
struct i2c_adapter *adap = NULL;
|
|
struct i2c_client *client = NULL;
|
|
char err_msg[64] = DEBUG_TRANSVR_STR_VAL;
|
|
|
|
adap = i2c_get_adapter(self->chan_id);
|
|
if(!adap){
|
|
snprintf(err_msg, sizeof(err_msg),
|
|
"can not get adap:%d", self->chan_id);
|
|
goto err_setup_i2c_client;
|
|
}
|
|
client = kzalloc(sizeof(*client), GFP_KERNEL);
|
|
if (!client){
|
|
snprintf(err_msg, sizeof(err_msg),
|
|
"can not kzalloc client:%d", self->chan_id);
|
|
goto err_setup_i2c_client;
|
|
}
|
|
client->adapter = adap;
|
|
self->i2c_client_p = client;
|
|
self->i2c_client_p->addr = VAL_TRANSVR_COMID_ARREESS;
|
|
return 0;
|
|
|
|
err_setup_i2c_client:
|
|
SWPS_ERR("%s: %s\n", __func__, err_msg);
|
|
return ERR_TRANSVR_UNEXCPT;
|
|
}
|
|
|
|
|
|
struct transvr_obj_s *
|
|
create_transvr_obj(char *swp_name,
|
|
int chan_id,
|
|
struct ioexp_obj_s *ioexp_obj_p,
|
|
int ioexp_virt_offset,
|
|
int transvr_type,
|
|
int chipset_type,
|
|
int run_mode){
|
|
|
|
struct transvr_obj_s *result_p;
|
|
struct eeprom_map_s *map_p;
|
|
char err_msg[64] = DEBUG_TRANSVR_STR_VAL;
|
|
|
|
/* Allocate transceiver object */
|
|
map_p = get_eeprom_map(transvr_type);
|
|
if (!map_p){
|
|
snprintf(err_msg, sizeof(err_msg),
|
|
"Invalid transvr_type:%d", transvr_type);
|
|
goto err_create_transvr_fail;
|
|
}
|
|
result_p = kzalloc(sizeof(*result_p), GFP_KERNEL);
|
|
if (!result_p){
|
|
snprintf(err_msg, sizeof(err_msg), "kzalloc fail");
|
|
goto err_create_transvr_fail;
|
|
}
|
|
/* Prepare static size attributes */
|
|
if (setup_transvr_ssize_attr(swp_name,
|
|
result_p,
|
|
map_p,
|
|
ioexp_obj_p,
|
|
ioexp_virt_offset,
|
|
transvr_type,
|
|
chipset_type,
|
|
chan_id,
|
|
run_mode) < 0){
|
|
goto err_create_transvr_sattr_fail;
|
|
}
|
|
|
|
/* Prepare call back functions of object */
|
|
if (setup_transvr_private_cb(result_p, transvr_type) < 0){
|
|
goto err_create_transvr_sattr_fail;
|
|
}
|
|
/* Prepare i2c client object */
|
|
if (setup_i2c_client(result_p) < 0){
|
|
goto err_create_transvr_sattr_fail;
|
|
}
|
|
return result_p;
|
|
err_create_transvr_sattr_fail:
|
|
kfree(result_p);
|
|
err_create_transvr_fail:
|
|
SWPS_ERR("%s: %s <chan>:%d <voff>:%d <type>:%d\n",
|
|
__func__, err_msg, chan_id, ioexp_virt_offset, transvr_type);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
_reload_transvr_obj(struct transvr_obj_s *self,
|
|
int new_type){
|
|
|
|
struct eeprom_map_s *new_map_p;
|
|
struct eeprom_map_s *old_map_p = self->eeprom_map_p;
|
|
struct i2c_client *old_i2c_p = self->i2c_client_p;
|
|
int old_type = self->type;
|
|
|
|
/* Change state to STATE_TRANSVR_INIT */
|
|
self->state = STATE_TRANSVR_INIT;
|
|
self->type = new_type;
|
|
/* Replace EEPROME map */
|
|
new_map_p = get_eeprom_map(new_type);
|
|
if (!new_map_p){
|
|
goto err_private_reload_func_1;
|
|
}
|
|
self->eeprom_map_p = new_map_p;
|
|
/* Reload i2c client */
|
|
if (setup_i2c_client(self) < 0){
|
|
goto err_private_reload_func_2;
|
|
}
|
|
if (setup_transvr_private_cb(self, new_type) < 0){
|
|
goto err_private_reload_func_3;
|
|
}
|
|
kfree(old_i2c_p);
|
|
return 0;
|
|
|
|
err_private_reload_func_3:
|
|
SWPS_INFO("%s: init() fail!\n", __func__);
|
|
kfree(old_i2c_p);
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
self->type = TRANSVR_TYPE_ERROR;
|
|
return -2;
|
|
|
|
err_private_reload_func_2:
|
|
self->eeprom_map_p = old_map_p;
|
|
self->i2c_client_p = old_i2c_p;
|
|
err_private_reload_func_1:
|
|
self->state = STATE_TRANSVR_UNEXCEPTED;
|
|
self->type = old_type;
|
|
SWPS_INFO("%s fail! <type>:0x%02x\n", __func__, new_type);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
reload_transvr_obj(struct transvr_obj_s *self,
|
|
int new_type){
|
|
|
|
int result_val = ERR_TRANSVR_UNEXCPT;
|
|
|
|
/* Reload phase */
|
|
result_val = _reload_transvr_obj(self, new_type);
|
|
if (result_val < 0){
|
|
SWPS_INFO("%s: reload phase fail! <err>:%d\n",
|
|
__func__, result_val);
|
|
return EVENT_TRANSVR_RELOAD_FAIL;
|
|
}
|
|
/* Initial phase */
|
|
result_val = _transvr_init_handler(self);
|
|
if (result_val < 0){
|
|
SWPS_INFO("%s: initial phase fail! <err>:%d\n",
|
|
__func__, result_val);
|
|
}
|
|
return result_val;
|
|
}
|
|
|
|
|
|
|
|
|