#include <linux/bits.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
#include <net/dsa.h>
#include "mxl862xx.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
#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 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_set_data(struct mxl862xx_priv *priv, u16 words)
{
int ret;
u16 cmd;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
if (ret < 0)
return ret;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1;
if (!(cmd < 2))
return -EINVAL;
cmd += MMD_API_SET_DATA_0;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
cmd | CTRL_BUSY_MASK);
if (ret < 0)
return ret;
return mxl862xx_busy_wait(priv);
}
static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words)
{
int ret;
u16 cmd;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET,
MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16));
if (ret < 0)
return ret;
cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (!(cmd > 0 && cmd < 3))
return -EINVAL;
cmd += MMD_API_GET_DATA_0;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
cmd | CTRL_BUSY_MASK);
if (ret < 0)
return ret;
return mxl862xx_busy_wait(priv);
}
static int mxl862xx_firmware_return(int ret)
{
if (WARN_ON(ret & GENMASK(31, 16)))
return -EINVAL;
return (s16)ret;
}
static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size,
bool quiet)
{
int ret;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size);
if (ret)
return ret;
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL,
cmd | CTRL_BUSY_MASK);
if (ret)
return ret;
ret = mxl862xx_busy_wait(priv);
if (ret)
return ret;
ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET);
if (ret < 0)
return ret;
ret = mxl862xx_firmware_return(ret);
if (ret < 0) {
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;
int ret, cmd_ret;
u16 max, 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; i < max; i++) {
u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE;
if (i && off == 0) {
ret = mxl862xx_set_data(priv, i);
if (ret < 0)
goto out;
}
ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off,
le16_to_cpu(data[i]));
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; 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 * 2 + 1) == size) {
*(uint8_t *)&data[i] = ret & 0xff;
} else {
data[i] = cpu_to_le16((u16)ret);
}
}
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;
}