#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/crc16.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
#include <linux/unaligned.h>
#include <net/dsa.h>
#include "mxl862xx.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
#define CTRL_CRC_FLAG BIT(14)
#define LEN_RET_LEN_MASK GENMASK(9, 0)
#define MXL862XX_MMD_REG_CTRL 0
#define MXL862XX_MMD_REG_LEN_RET 1
#define MXL862XX_MMD_REG_DATA_FIRST 2
#define MXL862XX_MMD_REG_DATA_LAST 95
#define MXL862XX_MMD_REG_DATA_MAX_SIZE \
(MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1)
#define MMD_API_SET_DATA_0 2
#define MMD_API_GET_DATA_0 5
#define MMD_API_RST_DATA 8
#define MXL862XX_SWITCH_RESET 0x9907
static void mxl862xx_crc_err_work_fn(struct work_struct *work)
{
struct mxl862xx_priv *priv = container_of(work, struct mxl862xx_priv,
crc_err_work);
struct dsa_port *dp;
rtnl_lock();
if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) {
dev_warn(&priv->mdiodev->dev,
"MDIO CRC error detected, shutting down all ports\n");
dsa_switch_for_each_cpu_port(dp, priv->ds)
dev_close(dp->conduit);
}
rtnl_unlock();
clear_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags);
}
#define MXL862XX_FW_CRC6_ERR (-1024)
#define MXL862XX_FW_CRC16_ERR (-1023)
static const u8 mxl862xx_crc6_table[256] = {
0x00, 0x2f, 0x31, 0x1e, 0x0d, 0x22, 0x3c, 0x13,
0x1a, 0x35, 0x2b, 0x04, 0x17, 0x38, 0x26, 0x09,
0x34, 0x1b, 0x05, 0x2a, 0x39, 0x16, 0x08, 0x27,
0x2e, 0x01, 0x1f, 0x30, 0x23, 0x0c, 0x12, 0x3d,
0x07, 0x28, 0x36, 0x19, 0x0a, 0x25, 0x3b, 0x14,
0x1d, 0x32, 0x2c, 0x03, 0x10, 0x3f, 0x21, 0x0e,
0x33, 0x1c, 0x02, 0x2d, 0x3e, 0x11, 0x0f, 0x20,
0x29, 0x06, 0x18, 0x37, 0x24, 0x0b, 0x15, 0x3a,
0x0e, 0x21, 0x3f, 0x10, 0x03, 0x2c, 0x32, 0x1d,
0x14, 0x3b, 0x25, 0x0a, 0x19, 0x36, 0x28, 0x07,
0x3a, 0x15, 0x0b, 0x24, 0x37, 0x18, 0x06, 0x29,
0x20, 0x0f, 0x11, 0x3e, 0x2d, 0x02, 0x1c, 0x33,
0x09, 0x26, 0x38, 0x17, 0x04, 0x2b, 0x35, 0x1a,
0x13, 0x3c, 0x22, 0x0d, 0x1e, 0x31, 0x2f, 0x00,
0x3d, 0x12, 0x0c, 0x23, 0x30, 0x1f, 0x01, 0x2e,
0x27, 0x08, 0x16, 0x39, 0x2a, 0x05, 0x1b, 0x34,
0x1c, 0x33, 0x2d, 0x02, 0x11, 0x3e, 0x20, 0x0f,
0x06, 0x29, 0x37, 0x18, 0x0b, 0x24, 0x3a, 0x15,
0x28, 0x07, 0x19, 0x36, 0x25, 0x0a, 0x14, 0x3b,
0x32, 0x1d, 0x03, 0x2c, 0x3f, 0x10, 0x0e, 0x21,
0x1b, 0x34, 0x2a, 0x05, 0x16, 0x39, 0x27, 0x08,
0x01, 0x2e, 0x30, 0x1f, 0x0c, 0x23, 0x3d, 0x12,
0x2f, 0x00, 0x1e, 0x31, 0x22, 0x0d, 0x13, 0x3c,
0x35, 0x1a, 0x04, 0x2b, 0x38, 0x17, 0x09, 0x26,
0x12, 0x3d, 0x23, 0x0c, 0x1f, 0x30, 0x2e, 0x01,
0x08, 0x27, 0x39, 0x16, 0x05, 0x2a, 0x34, 0x1b,
0x26, 0x09, 0x17, 0x38, 0x2b, 0x04, 0x1a, 0x35,
0x3c, 0x13, 0x0d, 0x22, 0x31, 0x1e, 0x00, 0x2f,
0x15, 0x3a, 0x24, 0x0b, 0x18, 0x37, 0x29, 0x06,
0x0f, 0x20, 0x3e, 0x11, 0x02, 0x2d, 0x33, 0x1c,
0x21, 0x0e, 0x10, 0x3f, 0x2c, 0x03, 0x1d, 0x32,
0x3b, 0x14, 0x0a, 0x25, 0x36, 0x19, 0x07, 0x28,
};
static u8 mxl862xx_crc6(u16 ctrl, u16 len_ret)
{
u32 data = ((u32)(len_ret & LEN_RET_LEN_MASK) << 16) | ctrl;
u8 crc = 0;
int i;
for (i = 0; i < sizeof(data); i++, data >>= 8)
crc = mxl862xx_crc6_table[(crc << 2) ^ (data & 0xff)] & 0x3f;
return crc;
}
static void mxl862xx_crc6_encode(u16 *pctrl, u16 *plen_ret)
{
u16 crc, ctrl, len_ret;
*pctrl |= BIT(13);
crc = mxl862xx_crc6(*pctrl, *plen_ret);
ctrl = (*pctrl & ~GENMASK(14, 13));
ctrl |= (crc & 0x30) << 9;
len_ret = *plen_ret | ((crc & 0x0f) << 10);
len_ret |= (*pctrl & BIT(14)) | ((*pctrl & BIT(13)) << 2);
*pctrl = ctrl;
*plen_ret = len_ret;
}
static int mxl862xx_crc6_verify(u16 ctrl, u16 len_ret, int *result)
{
u16 crc_recv, crc_calc;
crc_recv = ((ctrl >> 9) & 0x30) | ((len_ret >> 10) & 0x0f);
ctrl &= ~GENMASK(14, 13);
ctrl |= len_ret & BIT(14);
ctrl |= (len_ret & BIT(15)) >> 2;
crc_calc = mxl862xx_crc6(ctrl, len_ret);
if (crc_recv != crc_calc)
return -EIO;
*result = sign_extend32((len_ret & LEN_RET_LEN_MASK) |
((ctrl & CTRL_CRC_FLAG) >> 4), 10);
return 0;
}
static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr)
{
return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr);
}
static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data)
{
return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data);
}
static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv)
{
return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
}
static int mxl862xx_busy_wait(struct mxl862xx_priv *priv)
{
int val;
return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
!(val & CTRL_BUSY_MASK), 15, 500000);
}
static int mxl862xx_issue_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 len)
{
u16 ctrl_enc, len_enc;
int ret, fw_result;
ctrl_enc = cmd | CTRL_BUSY_MASK | CTRL_CRC_FLAG;
len_enc = len;
mxl862xx_crc6_encode(&ctrl_enc, &len_enc);
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, len_enc);
if (ret < 0)
return ret;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, ctrl_enc);
if (ret < 0)
return ret;
ret = mxl862xx_busy_wait(priv);
if (ret < 0)
return ret;
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL);
if (ret < 0)
return ret;
ctrl_enc = ret;
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
if (ret < 0)
return ret;
len_enc = ret;
ret = mxl862xx_crc6_verify(ctrl_enc, len_enc, &fw_result);
if (ret) {
if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
return -EIO;
}
return fw_result;
}
static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words)
{
u16 cmd;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1;
if (!(cmd < 2))
return -EINVAL;
cmd += MMD_API_SET_DATA_0;
return mxl862xx_issue_cmd(priv, cmd,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
}
static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words)
{
u16 cmd;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (!(cmd > 0 && cmd < 3))
return -EINVAL;
cmd += MMD_API_GET_DATA_0;
return mxl862xx_issue_cmd(priv, cmd,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
}
static int mxl862xx_rst_data(struct mxl862xx_priv *priv)
{
return mxl862xx_issue_cmd(priv, MMD_API_RST_DATA, 0);
}
#define RST_DATA_THRESHOLD 5
static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
bool quiet)
{
int ret;
ret = mxl862xx_issue_cmd(priv, cmd, size);
if (ret < 0) {
if ((ret == MXL862XX_FW_CRC6_ERR ||
ret == MXL862XX_FW_CRC16_ERR) &&
!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
if (!quiet)
dev_err(&priv->mdiodev->dev,
"CMD %04x returned error %d\n", cmd, ret);
return -EIO;
}
return ret;
}
int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
u16 size, bool read, bool quiet)
{
__le16 *data = _data;
bool use_rst = false;
unsigned int zeros;
int ret, cmd_ret;
u16 max, crc, i;
dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
max = (size + 1) / 2;
ret = mxl862xx_busy_wait(priv);
if (ret < 0)
goto out;
for (i = 0, zeros = 0; i < size / 2 && zeros < RST_DATA_THRESHOLD; i++)
if (!get_unaligned_le16(&data[i]))
zeros++;
if (zeros < RST_DATA_THRESHOLD && (size & 1) && !*(u8 *)&data[i])
zeros++;
if (zeros >= RST_DATA_THRESHOLD) {
ret = mxl862xx_rst_data(priv);
if (ret < 0)
goto out;
use_rst = true;
}
crc = crc16(0xffff, (const u8 *)data, size);
for (i = 0; i < max + 1; i++) {
u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
u16 val;
if (i && off == 0) {
ret = mxl862xx_set_data(priv, i);
if (ret < 0)
goto out;
}
if (i == max) {
val = (size & 1) ? crc >> 8 : crc;
} else if ((i * 2 + 1) == size) {
val = *(u8 *)&data[i] | ((crc & 0xff) << 8);
} else {
val = get_unaligned_le16(&data[i]);
}
if (use_rst && i < max && val == 0)
continue;
ret = mxl862xx_reg_write(priv,
MXL862XX_MMD_REG_DATA_FIRST + off,
val);
if (ret < 0)
goto out;
}
ret = mxl862xx_send_cmd(priv, cmd, size, quiet);
if (ret < 0 || !read)
goto out;
cmd_ret = ret;
for (i = 0; i < max + 1; i++) {
u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (i && off == 0) {
ret = mxl862xx_get_data(priv, i);
if (ret < 0)
goto out;
}
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off);
if (ret < 0)
goto out;
if (i == max) {
if (size & 1)
crc = (crc & 0x00ff) |
(((u16)ret & 0xff) << 8);
else
crc = (u16)ret;
} else if ((i * 2 + 1) == size) {
*(uint8_t *)&data[i] = ret & 0xff;
crc = (ret >> 8) & 0xff;
} else {
put_unaligned_le16((u16)ret, &data[i]);
}
}
if (crc16(0xffff, (const u8 *)data, size) != crc) {
if (!test_and_set_bit(MXL862XX_FLAG_CRC_ERR, &priv->flags))
schedule_work(&priv->crc_err_work);
ret = -EIO;
goto out;
}
ret = cmd_ret;
dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data);
out:
mutex_unlock(&priv->mdiodev->bus->mdio_lock);
return ret;
}
int mxl862xx_reset(struct mxl862xx_priv *priv)
{
int ret;
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0);
if (ret)
goto out;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET);
out:
mutex_unlock(&priv->mdiodev->bus->mdio_lock);
return ret;
}
void mxl862xx_host_init(struct mxl862xx_priv *priv)
{
INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
}
void mxl862xx_host_shutdown(struct mxl862xx_priv *priv)
{
cancel_work_sync(&priv->crc_err_work);
}