| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Based upon the MaxLinear SDK driver |
| * |
| * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> |
| * Copyright (C) 2025 John Crispin <john@phrozen.org> |
| * Copyright (C) 2024 MaxLinear Inc. |
| */ |
| |
| #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) |
| { |
| /* Only 16-bit values are valid. */ |
| if (WARN_ON(ret & GENMASK(31, 16))) |
| return -EINVAL; |
| |
| /* Interpret value as signed 16-bit integer. */ |
| 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; |
| |
| /* handle errors returned by the firmware as -EIO |
| * The firmware is based on Zephyr OS and uses the errors as |
| * defined in errno.h of Zephyr OS. See |
| * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h |
| */ |
| 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) { |
| /* Send command to set data when every |
| * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written. |
| */ |
| 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; |
| |
| /* store result of mxl862xx_send_cmd() */ |
| cmd_ret = ret; |
| |
| for (i = 0; i < max; i++) { |
| u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; |
| |
| if (i && off == 0) { |
| /* Send command to fetch next batch of data when every |
| * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read. |
| */ |
| 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) { |
| /* Special handling for last BYTE if it's not WORD |
| * aligned to avoid writing beyond the allocated data |
| * structure. |
| */ |
| *(uint8_t *)&data[i] = ret & 0xff; |
| } else { |
| data[i] = cpu_to_le16((u16)ret); |
| } |
| } |
| |
| /* on success return the result of the mxl862xx_send_cmd() */ |
| 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); |
| |
| /* Software reset */ |
| 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; |
| } |