Commit 7942dcdb authored by Adam Wujek's avatar Adam Wujek

shell/cmd_sfp: add support for tunable SFPs

Allow tune by channel and wavelength.
Display info specific for tunable SFPs.
Signed-off-by: 's avatarAdam Wujek <dev_public@wujek.eu>
parent fc9771fa
......@@ -138,3 +138,172 @@ int sfp_match(int force)
sfp_info.sfp_in_db = SFP_MATCHED;
return 0;
}
// #define DEBUG_I2C
/* lm32's compiler does not remove strings used in a function if
* the function is optimized out. So if an option is not used don't include
* (the not optimized out strings in) functions. Ugly but can be
* removed when LM32's support is dropped. */
#if (defined CONFIG_INSITU_CALIB && defined CONFIG_ARCH_LM32) || defined CONFIG_ARCH_RISCV
static void sfp_i2c_read(uint8_t addr, uint8_t reg, uint8_t * data, uint16_t len)
{
#ifdef DEBUG_I2C
pp_printf("Reading from 0x%02x, reg=0x%02x len=0x%02x: ", addr, reg, len);
#endif
bb_i2c_init( &dev_i2c_sfp );
bb_i2c_start(&dev_i2c_sfp);
bb_i2c_put_byte(&dev_i2c_sfp, addr<<1);
/* select register to read */
bb_i2c_put_byte(&dev_i2c_sfp, reg);
bb_i2c_repeat_start(&dev_i2c_sfp);
bb_i2c_put_byte(&dev_i2c_sfp, addr << 1 | BB_I2C_WRITE);
while (len > 0) {
bb_i2c_get_byte(&dev_i2c_sfp, data, len == 1);
#ifdef DEBUG_I2C
pp_printf(" %02x", *data);
#endif
len--;
data++;
}
bb_i2c_stop(&dev_i2c_sfp);
#ifdef DEBUG_I2C
puts(", done!\n");
#endif
}
static void sfp_i2c_write(uint8_t addr, uint8_t reg, uint8_t * data, uint16_t len)
{
bb_i2c_init( &dev_i2c_sfp );
bb_i2c_start(&dev_i2c_sfp);
bb_i2c_put_byte(&dev_i2c_sfp, addr<<1);
bb_i2c_put_byte(&dev_i2c_sfp, reg);
#ifdef DEBUG_I2C
pp_printf("Writing to %02x, reg=%02x:\n", addr, reg);
#endif
while (len > 0) {
#ifdef DEBUG_I2C
pp_printf(" %02x", *data);
#endif
bb_i2c_put_byte(&dev_i2c_sfp, *data);
len--;
data++;
}
bb_i2c_stop(&dev_i2c_sfp);
#ifdef DEBUG_I2C
puts("\ndone!\n");
#endif
}
/* reset i2c bus
* can be useful if WRPC was reset in the middle of i2c transaction */
void sfp_i2c_reset(void)
{
}
/* selects the page for the upper 128 bytes of address space A2 */
void sfp_a2_select_page(uint8_t page)
{
sfp_i2c_write(I2C_SFP_DOM_ADDRESS, SFP_A2_PAGE_SELECT_REG, &page, 1);
}
uint8_t sfp_a2_read_u8(uint8_t reg)
{
uint8_t data;
sfp_i2c_read(I2C_SFP_DOM_ADDRESS, reg, &data, 1);
return data;
}
uint16_t sfp_a2_read_u16(uint8_t reg)
{
uint8_t data[2];
uint16_t ret;
sfp_i2c_read(I2C_SFP_DOM_ADDRESS, reg, data, 2);
ret = data[0] << 8;
ret |= data[1];
return ret;
}
void sfp_a2_write_u16(uint8_t reg, uint16_t value)
{
uint8_t data[2];
data[0] = 0xFF & (value >> 8);
data[1] = 0xFF & value;
sfp_i2c_write(I2C_SFP_DOM_ADDRESS, reg, data, 2);
}
void sfp_a2_write_u8(uint8_t reg, uint8_t value)
{
sfp_i2c_write(I2C_SFP_DOM_ADDRESS, reg, &value, 1);
}
int sfp_tune_ch(const char *args)
{
uint8_t tmp8;
uint8_t tmp16;
uint16_t val;
val = atoi(args);
if (val > 500) {
/* Assume tuning by wavelength */
val *= 20;
/* Find the decimal part of wavelength */
while (*args) {
if (*args == '.') {
char atoi_buff[2];
/* decimal point found */
args++;
atoi_buff[1] = 0;
atoi_buff[0] = *args;
/* Number of 0.1nm. val is in units of 0.05nm */
val += atoi(atoi_buff) * 10 / 5;
/* Wavelength is in units of 0.05, so it is
* enough to check the second char. If is 5,
* add 1 to val. Otherwise ignore. */
if (*(args + 1) == '5')
val++;
break;
}
args++;
}
}
sfp_a2_select_page(SFP_A2_PAGE_CONTROL_FUNC);
if (val > 500) {
pp_printf("Tuning SFP to wavelength: %d.%02d (raw: %d)\n",
(val * 5)/100, (val * 5) % 100, val);
sfp_a2_write_u16(SFP_A2_CTRL_WL_SET_REG, val);
} else {
pp_printf("Tuning SFP to channel: %d\n", val);
sfp_a2_write_u16(SFP_A2_CTRL_CHNO_SET_REG, val);
}
tmp8 = sfp_a2_read_u8(SFP_A2_CTRL_CUR_STATUS_REG);
pp_printf("Status reg 0x%02x, TX tune %d\n", tmp8, tmp8 & SFP_A2_CTRL_CUR_STATUS_TXTUNE);
tmp8 = sfp_a2_read_u8(SFP_A2_CTRL_LATCH_STATUS_REG);
pp_printf("latch reg 0x%02x\n", tmp8);
tmp16 = sfp_a2_read_u16(SFP_A2_CTRL_FREQ_ERR_REG);
pp_printf("freq error 0x%02x\n", tmp16);
tmp16 = sfp_a2_read_u16(SFP_A2_CTRL_WL_ERR_REG);
pp_printf("wavelength error 0x%02x\n", tmp16);
return 0;
}
#endif
......@@ -2371,6 +2371,21 @@ the build of WRPC.
\code{sfp show} & prints all SFP transceivers stored in database \\
\code{sfp info} & prints detailed information about the plugged SFP.
Can show monitoring data like temperature, RX and TX power
if supported.
For tunable SFP's another additional set of parameters is
displayed like: supported channels, current channel or
wavelength.\\
\code{sfp tune <channel|wavelength>} & tunes the TX of a plugged SFP. Command
can take \textit{channel}
(not an ITU channel!) or
\textit{wavelength} in nm.
Available if
\texttt{CONFIG\_INSITU\_CALIB} is set.
\\
\code{stat <on|off>} & without parameter, toggles reporting of loggable
statistics. You can pass \texttt{on} or \texttt{off} as argument as an
alternative to toggling \\
......
#ifndef __LIBWR_SHW_SFPLIB_H
#define __LIBWR_SHW_SFPLIB_H
//address from AT24C01 datasheet (1k, all address lines shorted to the ground)
/* address from AT24C01 datasheet (1k, all address lines shorted to the ground)
* Equivalent of 0xA0. */
#define I2C_SFP_ADDRESS 0x50
// From SFF-8472, but right-shifted one bit as I2C addresses are only 7 bits.
/* From SFF-8472, but right-shifted one bit as I2C addresses are only 7 bits.
* Equivalent of 0xA2. */
#define I2C_SFP_DOM_ADDRESS 0x51
#define SFP_FLAG_CLASS_DATA (1 << 0)
......@@ -33,6 +35,44 @@
accessing information at 2-wire serial
address A2h. */
#define SFP_OPTION_TUNABLE (1 << 6)
#define SFP_DITHER_TX (1 << 2)
#define SFP_TUNABLE_CHANNEL (1 << 1)
#define SFP_TUNABLE_WAVELENGTH (1 << 0)
/* ====================================================================
* Extension for wavelength tuning using SFF8690
* ====================================================================
*/
#define SFP_A2_PAGE_SELECT_REG 127
#define SFP_A2_CTRL_DITHERING_REG 128
#define SFP_A2_CTRL_LFL1_REG 132
#define SFP_A2_CTRL_LFL2_REG 134
#define SFP_A2_CTRL_LFH1_REG 136
#define SFP_A2_CTRL_LFH2_REG 138
#define SFP_A2_CTRL_LGRID_REG 140
#define SFP_A2_CTRL_CHNO_SET_REG 144
#define SFP_A2_CTRL_WL_SET_REG 146
#define SFP_A2_CTRL_TX_DITHER_REG 151
#define SFP_A2_CTRL_FREQ_ERR_REG 152
#define SFP_A2_CTRL_WL_ERR_REG 154
#define SFP_A2_CTRL_CUR_STATUS_REG 168
#define SFP_A2_CTRL_LATCH_STATUS_REG 172
#define SFP_A2_PAGE_CONTROL_FUNC 0x2
#define SFP_A2_CTRL_TX_DITHER_FALG 0x1
#define SFP_A2_CTRL_CUR_STATUS_TEC_FAULT 0x6
#define SFP_A2_CTRL_CUR_STATUS_WL_UNLOCK 0x5
#define SFP_A2_CTRL_CUR_STATUS_TXTUNE 0x4
#define SFP_A2_CTRL_LATCH_STATUS_TEC_FAULT 6
#define SFP_A2_CTRL_LATCH_STATUS_WL_UNLOCKED 5
#define SFP_A2_CTRL_LATCH_STATUS_BAD_CH 4
#define SFP_A2_CTRL_LATCH_STATUS_NEW_CH 3
#define SFP_A2_CTRL_LATCH_STATUS_UNSUPP_TX_DITHER 2
struct shw_sfp_caldata {
uint32_t flags;
......
......@@ -59,4 +59,11 @@ int sfp_match(int force);
/* update dom data */
int sfp_dom_update(void);
void sfp_a2_select_page(uint8_t page);
uint8_t sfp_a2_read_u8(uint8_t reg);
uint16_t sfp_a2_read_u16(uint8_t reg);
void sfp_a2_write_u8(uint8_t reg, uint8_t value);
void sfp_a2_write_u16(uint8_t reg, uint16_t value);
int sfp_tune_ch(const char *args);
#endif
......@@ -31,6 +31,13 @@
#include "dev/endpoint.h"
#include "sfp.h"
#include "libwr/sfp_lib.h"
#ifdef CONFIG_INSITU_CALIB
#define HAS_INSITU_CALIB 1
#else
#define HAS_INSITU_CALIB 0
#endif
// extern struct shw_sfp_header sfp_header;
extern struct shw_sfp_dom sfp_dom;
......@@ -72,12 +79,53 @@ void print_info(void)
tmp = ((sfp_dom->rx_pow[0] << 8) + sfp_dom->rx_pow[1]);
pp_printf("RX power: %d.%04d mW\n", tmp / 10000, tmp % 10000);
}
if (HAS_INSITU_CALIB) {
uint8_t tmp8;
uint16_t tmp16;
uint32_t first_freq_in_01;
uint32_t last_freq_in_01;
pp_printf("SFP tunable: %s\n",
sfp_header->options[1] & SFP_OPTION_TUNABLE
? "true" : "false");
sfp_a2_select_page(SFP_A2_PAGE_CONTROL_FUNC);
tmp8 = sfp_a2_read_u8(SFP_A2_CTRL_DITHERING_REG);
pp_printf("Dither TX: %s\n",
tmp8 & SFP_DITHER_TX ? "true" : "false");
pp_printf("Tunable by channel: %s\n",
tmp8 & SFP_TUNABLE_CHANNEL ? "true" : "false");
pp_printf("Tunable by wavelength: %s\n",
tmp8 & SFP_TUNABLE_WAVELENGTH ? "true" : "false");
first_freq_in_01 = sfp_a2_read_u16(SFP_A2_CTRL_LFL1_REG) * 10000
+ sfp_a2_read_u16(SFP_A2_CTRL_LFL2_REG);
pp_printf("Laser first freq: %ld.%04ld THz\n",
first_freq_in_01 / 10000,
first_freq_in_01 % 10000);
last_freq_in_01 = sfp_a2_read_u16(SFP_A2_CTRL_LFH1_REG) * 10000
+ sfp_a2_read_u16(SFP_A2_CTRL_LFH2_REG);
pp_printf("Laser last freq: %ld.%04ld THz\n",
last_freq_in_01 / 10000,
last_freq_in_01 % 10000);
tmp16 = sfp_a2_read_u16(SFP_A2_CTRL_LGRID_REG);
pp_printf("Laser's grid support: %d.%d GHz\n",
tmp16 / 10, tmp16 % 10);
pp_printf("Channels supported: 1..%ld\n",
1 + (last_freq_in_01 - first_freq_in_01) / tmp16);
tmp16 = sfp_a2_read_u16(SFP_A2_CTRL_CHNO_SET_REG);
pp_printf("Current channel number: %d\n", tmp16);
tmp16 = sfp_a2_read_u16(SFP_A2_CTRL_WL_SET_REG);
pp_printf("Current wavelength: %d.%02d nm\n", tmp16 / 20,
(tmp16 % 20) * 5);
}
}
static int cmd_sfp(const char *args[])
{
int8_t sfpcount = 1, i, temp, ret;
struct s_sfpinfo sfp;
struct shw_sfp_header *sfp_header;
sfp_header = sfp_info.sfp_header;
if (!args[0]) {
pp_printf("Wrong parameter\n");
......@@ -166,6 +214,16 @@ static int cmd_sfp(const char *args[])
} else if (!strcasecmp(args[0], "info")) {
/* DOM data is updated periodically by a task */
print_info();
} else if (HAS_INSITU_CALIB && !strcasecmp(args[0], "tune")) {
if (!(sfp_header->options[1] & SFP_OPTION_TUNABLE)) {
pp_printf("SFP not tunable!\n");
return -ENODEV;
}
if (!args[1]) {
pp_printf("tune args: %d\n", atoi(args[1]));
return -EINVAL;
}
sfp_tune_ch(args[1]);
} else {
pp_printf("Wrong parameter\n");
return -EINVAL;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment