diff options
Diffstat (limited to 'drivers/net/pse-pd')
| -rw-r--r-- | drivers/net/pse-pd/Kconfig | 83 | ||||
| -rw-r--r-- | drivers/net/pse-pd/Makefile | 12 | ||||
| -rw-r--r-- | drivers/net/pse-pd/pd692x0.c | 1889 | ||||
| -rw-r--r-- | drivers/net/pse-pd/pse_core.c | 1983 | ||||
| -rw-r--r-- | drivers/net/pse-pd/pse_regulator.c | 155 | ||||
| -rw-r--r-- | drivers/net/pse-pd/realtek-pse-mcu-core.c | 998 | ||||
| -rw-r--r-- | drivers/net/pse-pd/realtek-pse-mcu-i2c.c | 148 | ||||
| -rw-r--r-- | drivers/net/pse-pd/realtek-pse-mcu-uart.c | 164 | ||||
| -rw-r--r-- | drivers/net/pse-pd/realtek-pse-mcu.h | 94 | ||||
| -rw-r--r-- | drivers/net/pse-pd/si3474.c | 578 | ||||
| -rw-r--r-- | drivers/net/pse-pd/tps23881.c | 1558 |
11 files changed, 7662 insertions, 0 deletions
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig new file mode 100644 index 000000000..a0f2ae668 --- /dev/null +++ b/drivers/net/pse-pd/Kconfig @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Ethernet Power Sourcing Equipment drivers +# + +menuconfig PSE_CONTROLLER + bool "Ethernet Power Sourcing Equipment Support" + depends on REGULATOR + help + Generic Power Sourcing Equipment Controller support. + + If unsure, say no. + +if PSE_CONTROLLER + +config PSE_REALTEK_MCU + tristate + help + Shared core for the Realtek PSE MCU driver. This is selected + automatically by the transport options below. + +config PSE_REALTEK_MCU_I2C + tristate "Realtek PSE MCU driver (I2C transport)" + depends on I2C + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via I2C/SMBus. The MCU exposes a message-based protocol; the actual + PSE silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-i2c. + +config PSE_REALTEK_MCU_UART + tristate "Realtek PSE MCU driver (UART transport)" + depends on SERIAL_DEV_BUS + select PSE_REALTEK_MCU + help + Driver for the microcontroller (MCU) that fronts the PSE + hardware on various Realtek-based managed switches, attached + via UART. The MCU exposes a message-based protocol; the actual PSE + silicon is not accessed directly. To compile this driver as a + module, choose M here: the module will be called realtek-pse-mcu-uart. + +config PSE_REGULATOR + tristate "Regulator based PSE controller" + help + This module provides support for simple regulator based Ethernet Power + Sourcing Equipment without automatic classification support. For + example for basic implementation of PoDL (802.3bu) specification. + +config PSE_PD692X0 + tristate "PD692X0 PSE controller" + depends on I2C + select FW_LOADER + select FW_UPLOAD + help + This module provides support for PD692x0 regulator based Ethernet + Power Sourcing Equipment. + + To compile this driver as a module, choose M here: the + module will be called pd692x0. + +config PSE_SI3474 + tristate "Si3474 PSE controller" + depends on I2C + help + This module provides support for Si3474 regulator based Ethernet + Power Sourcing Equipment. + Only 4-pair PSE configurations are supported. + + To compile this driver as a module, choose M here: the + module will be called si3474. + +config PSE_TPS23881 + tristate "TPS23881 PSE controller" + depends on I2C + help + This module provides support for TPS23881 regulator based Ethernet + Power Sourcing Equipment. + + To compile this driver as a module, choose M here: the + module will be called tps23881. +endif diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile new file mode 100644 index 000000000..9cca5900f --- /dev/null +++ b/drivers/net/pse-pd/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for Linux PSE drivers + +obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o + +obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o +obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o +obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o +obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o +obj-$(CONFIG_PSE_PD692X0) += pd692x0.o +obj-$(CONFIG_PSE_SI3474) += si3474.o +obj-$(CONFIG_PSE_TPS23881) += tps23881.o diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c new file mode 100644 index 000000000..209de9cec --- /dev/null +++ b/drivers/net/pse-pd/pd692x0.c @@ -0,0 +1,1889 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Microchip PD692X0 PoE PSE Controller driver (I2C bus) + * + * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com> + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pse-pd/pse.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#define PD692X0_PSE_NAME "pd692x0_pse" + +#define PD692X0_MAX_PIS 48 +#define PD692X0_MAX_MANAGERS 12 +#define PD692X0_MAX_MANAGER_PORTS 8 +#define PD692X0_MAX_HW_PORTS (PD692X0_MAX_MANAGERS * PD692X0_MAX_MANAGER_PORTS) + +#define PD69200_BT_PROD_VER 24 +#define PD69210_BT_PROD_VER 26 +#define PD69220_BT_PROD_VER 29 + +#define PD692X0_FW_MAJ_VER 3 +#define PD692X0_FW_MIN_VER 5 +#define PD692X0_FW_PATCH_VER 5 + +#define PD692X0_USER_BYTE 42 + +enum pd692x0_fw_state { + PD692X0_FW_UNKNOWN, + PD692X0_FW_OK, + PD692X0_FW_BROKEN, + PD692X0_FW_NEED_UPDATE, + PD692X0_FW_PREPARE, + PD692X0_FW_WRITE, + PD692X0_FW_COMPLETE, +}; + +struct pd692x0_msg { + u8 key; + u8 echo; + u8 sub[3]; + u8 data[8]; + __be16 chksum; +} __packed; + +struct pd692x0_msg_ver { + u8 prod; + u8 maj_sw_ver; + u8 min_sw_ver; + u8 pa_sw_ver; + u8 param; + u8 build; +}; + +enum { + PD692X0_KEY_CMD, + PD692X0_KEY_PRG, + PD692X0_KEY_REQ, + PD692X0_KEY_TLM, + PD692X0_KEY_TEST, + PD692X0_KEY_REPORT = 0x52 +}; + +enum { + PD692X0_MSG_RESET, + PD692X0_MSG_GET_SYS_STATUS, + PD692X0_MSG_GET_SW_VER, + PD692X0_MSG_SET_TMP_PORT_MATRIX, + PD692X0_MSG_PRG_PORT_MATRIX, + PD692X0_MSG_SET_PORT_PARAM, + PD692X0_MSG_GET_PORT_STATUS, + PD692X0_MSG_DOWNLOAD_CMD, + PD692X0_MSG_GET_PORT_CLASS, + PD692X0_MSG_GET_PORT_MEAS, + PD692X0_MSG_GET_PORT_PARAM, + PD692X0_MSG_GET_POWER_BANK, + PD692X0_MSG_SET_POWER_BANK, + PD692X0_MSG_SET_USER_BYTE, + + /* add new message above here */ + PD692X0_MSG_CNT +}; + +struct pd692x0_matrix { + u8 hw_port_a; + u8 hw_port_b; +}; + +struct pd692x0_priv { + struct i2c_client *client; + struct pse_controller_dev pcdev; + struct device_node *np; + + enum pd692x0_fw_state fw_state; + struct fw_upload *fwl; + bool cancel_request; + + u8 msg_id; + bool last_cmd_key; + unsigned long last_cmd_key_time; + + bool cfg_saved; + enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS]; + struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS]; + int manager_pw_budget[PD692X0_MAX_MANAGERS]; + int nmanagers; + struct pd692x0_matrix *port_matrix; +}; + +/* Template list of communication messages. The non-null bytes defined here + * constitute the fixed portion of the messages. The remaining bytes will + * be configured later within the functions. Refer to the "PD692x0 BT Serial + * Communication Protocol User Guide" for comprehensive details on messages + * content. + */ +static const struct pd692x0_msg pd692x0_msg_template_list[PD692X0_MSG_CNT] = { + [PD692X0_MSG_RESET] = { + .key = PD692X0_KEY_CMD, + .sub = {0x07, 0x55, 0x00}, + .data = {0x55, 0x00, 0x55, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_SYS_STATUS] = { + .key = PD692X0_KEY_REQ, + .sub = {0x07, 0xd0, 0x4e}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_SW_VER] = { + .key = PD692X0_KEY_REQ, + .sub = {0x07, 0x1e, 0x21}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_SET_TMP_PORT_MATRIX] = { + .key = PD692X0_KEY_CMD, + .sub = {0x05, 0x43}, + .data = { 0, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_PRG_PORT_MATRIX] = { + .key = PD692X0_KEY_CMD, + .sub = {0x07, 0x43, 0x4e}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_SET_PORT_PARAM] = { + .key = PD692X0_KEY_CMD, + .sub = {0x05, 0xc0}, + .data = { 0xf, 0xff, 0xff, 0xff, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_PORT_STATUS] = { + .key = PD692X0_KEY_REQ, + .sub = {0x05, 0xc1}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_DOWNLOAD_CMD] = { + .key = PD692X0_KEY_PRG, + .sub = {0xff, 0x99, 0x15}, + .data = {0x16, 0x16, 0x99, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_PORT_CLASS] = { + .key = PD692X0_KEY_REQ, + .sub = {0x05, 0xc4}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_PORT_MEAS] = { + .key = PD692X0_KEY_REQ, + .sub = {0x05, 0xc5}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_PORT_PARAM] = { + .key = PD692X0_KEY_REQ, + .sub = {0x05, 0xc0}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_GET_POWER_BANK] = { + .key = PD692X0_KEY_REQ, + .sub = {0x07, 0x0b, 0x57}, + .data = { 0, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, + [PD692X0_MSG_SET_POWER_BANK] = { + .key = PD692X0_KEY_CMD, + .sub = {0x07, 0x0b, 0x57}, + }, + [PD692X0_MSG_SET_USER_BYTE] = { + .key = PD692X0_KEY_PRG, + .sub = {0x41, PD692X0_USER_BYTE, 0x4e}, + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, +}; + +static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo) +{ + u8 *data = (u8 *)msg; + u16 chksum = 0; + int i; + + msg->echo = echo++; + if (echo == 0xff) + echo = 0; + + for (i = 0; i < sizeof(*msg) - sizeof(msg->chksum); i++) + chksum += data[i]; + + msg->chksum = cpu_to_be16(chksum); + + return echo; +} + +static int pd692x0_send_msg(struct pd692x0_priv *priv, struct pd692x0_msg *msg) +{ + const struct i2c_client *client = priv->client; + int ret; + + if (msg->key == PD692X0_KEY_CMD && priv->last_cmd_key) { + int cmd_msleep; + + cmd_msleep = 30 - jiffies_to_msecs(jiffies - priv->last_cmd_key_time); + if (cmd_msleep > 0) + msleep(cmd_msleep); + } + + /* Add echo and checksum bytes to the message */ + priv->msg_id = pd692x0_build_msg(msg, priv->msg_id); + + ret = i2c_master_send(client, (u8 *)msg, sizeof(*msg)); + if (ret != sizeof(*msg)) + return -EIO; + + return 0; +} + +static int pd692x0_reset(struct pd692x0_priv *priv) +{ + const struct i2c_client *client = priv->client; + struct pd692x0_msg msg, buf = {0}; + int ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_RESET]; + ret = pd692x0_send_msg(priv, &msg); + if (ret) { + dev_err(&client->dev, + "Failed to reset the controller (%pe)\n", ERR_PTR(ret)); + return ret; + } + + msleep(30); + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret != sizeof(buf)) + return ret < 0 ? ret : -EIO; + + /* Is the reply a successful report message */ + if (buf.key != PD692X0_KEY_REPORT || buf.sub[0] || buf.sub[1]) + return -EIO; + + msleep(300); + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret != sizeof(buf)) + return ret < 0 ? ret : -EIO; + + /* Is the boot status without error */ + if (buf.key != 0x03 || buf.echo != 0xff || buf.sub[0] & 0x1) { + dev_err(&client->dev, "PSE controller error\n"); + return -EIO; + } + + return 0; +} + +static bool pd692x0_try_recv_msg(const struct i2c_client *client, + struct pd692x0_msg *msg, + struct pd692x0_msg *buf) +{ + /* Wait 30ms before readback as mandated by the protocol */ + msleep(30); + + memset(buf, 0, sizeof(*buf)); + i2c_master_recv(client, (u8 *)buf, sizeof(*buf)); + if (buf->key) + return 0; + + msleep(100); + + memset(buf, 0, sizeof(*buf)); + i2c_master_recv(client, (u8 *)buf, sizeof(*buf)); + if (buf->key) + return 0; + + return 1; +} + +/* Implementation of I2C communication, specifically addressing scenarios + * involving communication loss. Refer to the "Synchronization During + * Communication Loss" section in the Communication Protocol document for + * further details. + */ +static int pd692x0_recv_msg(struct pd692x0_priv *priv, + struct pd692x0_msg *msg, + struct pd692x0_msg *buf) +{ + const struct i2c_client *client = priv->client; + int ret; + + ret = pd692x0_try_recv_msg(client, msg, buf); + if (!ret) + goto out_success; + + dev_warn(&client->dev, + "Communication lost, rtnl is locked until communication is back!"); + + ret = pd692x0_send_msg(priv, msg); + if (ret) + return ret; + + ret = pd692x0_try_recv_msg(client, msg, buf); + if (!ret) + goto out_success2; + + msleep(10000); + + ret = pd692x0_send_msg(priv, msg); + if (ret) + return ret; + + ret = pd692x0_try_recv_msg(client, msg, buf); + if (!ret) + goto out_success2; + + return pd692x0_reset(priv); + +out_success2: + dev_warn(&client->dev, "Communication is back, rtnl is unlocked!"); +out_success: + if (msg->key == PD692X0_KEY_CMD) { + priv->last_cmd_key = true; + priv->last_cmd_key_time = jiffies; + } else { + priv->last_cmd_key = false; + } + + return 0; +} + +static int pd692x0_sendrecv_msg(struct pd692x0_priv *priv, + struct pd692x0_msg *msg, + struct pd692x0_msg *buf) +{ + struct device *dev = &priv->client->dev; + int ret; + + ret = pd692x0_send_msg(priv, msg); + if (ret) + return ret; + + ret = pd692x0_recv_msg(priv, msg, buf); + if (ret) + return ret; + + if (msg->echo != buf->echo) { + dev_err(dev, + "Wrong match in message ID, expect %d received %d.\n", + msg->echo, buf->echo); + return -EIO; + } + + /* If the reply is a report message is it successful */ + if (buf->key == PD692X0_KEY_REPORT && + (buf->sub[0] || buf->sub[1])) { + return -EIO; + } + + return 0; +} + +static struct pd692x0_priv *to_pd692x0_priv(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct pd692x0_priv, pcdev); +} + +static int pd692x0_fw_unavailable(struct pd692x0_priv *priv) +{ + switch (priv->fw_state) { + case PD692X0_FW_OK: + return 0; + case PD692X0_FW_PREPARE: + case PD692X0_FW_WRITE: + case PD692X0_FW_COMPLETE: + dev_err(&priv->client->dev, "Firmware update in progress!\n"); + return -EBUSY; + case PD692X0_FW_BROKEN: + case PD692X0_FW_NEED_UPDATE: + default: + dev_err(&priv->client->dev, + "Firmware issue. Please update it!\n"); + return -EOPNOTSUPP; + } +} + +static int pd692x0_pi_enable(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) + return 0; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; + msg.data[0] = 0x1; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + + return 0; +} + +static int pd692x0_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED) + return 0; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; + msg.data[0] = 0x0; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + + return 0; +} + +struct pd692x0_pse_ext_state_mapping { + u32 status_code; + enum ethtool_c33_pse_ext_state pse_ext_state; + u32 pse_ext_substate; +}; + +static const struct pd692x0_pse_ext_state_mapping +pd692x0_pse_ext_state_map[] = { + {0x06, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_HIGH_VOLTAGE}, + {0x07, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_LOW_VOLTAGE}, + {0x08, ETHTOOL_C33_PSE_EXT_STATE_MR_PSE_ENABLE, + ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_PSE_ENABLE_DISABLE_PIN_ACTIVE}, + {0x0C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_NON_EXISTING_PORT}, + {0x11, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNDEFINED_PORT}, + {0x12, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT}, + {0x1B, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_DET_IN_PROCESS}, + {0x1C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS}, + {0x1E, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID, + ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_DETECTED_UNDERLOAD}, + {0x1F, ETHTOOL_C33_PSE_EXT_STATE_OVLD_DETECTED, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OVLD_DETECTED_OVERLOAD}, + {0x20, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE, + ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_BUDGET_EXCEEDED}, + {0x21, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT}, + {0x22, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_CONFIG_CHANGE}, + {0x24, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_VOLTAGE_INJECTION}, + {0x25, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS}, + {0x34, ETHTOOL_C33_PSE_EXT_STATE_SHORT_DETECTED, + ETHTOOL_C33_PSE_EXT_SUBSTATE_SHORT_DETECTED_SHORT_CONDITION}, + {0x35, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP}, + {0x36, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP}, + {0x37, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS}, + {0x3C, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE, + ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PORT_PW_LIMIT_EXCEEDS_CONTROLLER_BUDGET}, + {0x3D, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE, + ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PD_REQUEST_EXCEEDS_PORT_LIMIT}, + {0x41, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE, + ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_HW_PW_LIMIT}, + {0x43, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION, + ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS}, + {0xA7, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED, + ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_CONNECTION_CHECK_ERROR}, + {0xA8, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID, + ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_CONNECTION_OPEN}, + { /* sentinel */ } +}; + +static int +pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id, + struct pse_ext_state_info *ext_state_info) +{ + struct ethtool_c33_pse_ext_state_info *c33_ext_state_info; + const struct pd692x0_pse_ext_state_mapping *ext_state_map; + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + c33_ext_state_info = &ext_state_info->c33_ext_state_info; + ext_state_map = pd692x0_pse_ext_state_map; + while (ext_state_map->status_code) { + if (ext_state_map->status_code == buf.sub[0]) { + c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state; + c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate; + return 0; + } + ext_state_map++; + } + + return 0; +} + +struct pd692x0_class_pw { + int class; + int class_cfg_value; + int class_pw; + int max_added_class_pw; +}; + +#define PD692X0_CLASS_PW_TABLE_SIZE 4 +/* 4/2 pairs class configuration power table in compliance mode. + * Need to be arranged in ascending order of power support. + */ +static const struct pd692x0_class_pw +pd692x0_class_pw_table[PD692X0_CLASS_PW_TABLE_SIZE] = { + {.class = 3, .class_cfg_value = 0x3, .class_pw = 15000, .max_added_class_pw = 3100}, + {.class = 4, .class_cfg_value = 0x2, .class_pw = 30000, .max_added_class_pw = 8000}, + {.class = 6, .class_cfg_value = 0x1, .class_pw = 60000, .max_added_class_pw = 5000}, + {.class = 8, .class_cfg_value = 0x0, .class_pw = 90000, .max_added_class_pw = 7500}, +}; + +static int pd692x0_pi_get_pw_from_table(int op_mode, int added_pw) +{ + const struct pd692x0_class_pw *pw_table; + int i; + + pw_table = pd692x0_class_pw_table; + for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) { + if (pw_table->class_cfg_value == op_mode) + return pw_table->class_pw + added_pw * 100; + } + + return -ERANGE; +} + +static int pd692x0_pi_set_pw_from_table(struct device *dev, + struct pd692x0_msg *msg, int pw) +{ + const struct pd692x0_class_pw *pw_table; + int i; + + pw_table = pd692x0_class_pw_table; + if (pw < pw_table->class_pw) { + dev_err(dev, + "Power limit %dmW not supported. Ranges minimal available: [%d-%d]\n", + pw, + pw_table->class_pw, + pw_table->class_pw + pw_table->max_added_class_pw); + return -ERANGE; + } + + for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) { + if (pw > (pw_table->class_pw + pw_table->max_added_class_pw)) + continue; + + if (pw < pw_table->class_pw) { + dev_err(dev, + "Power limit %dmW not supported. Ranges available: [%d-%d] or [%d-%d]\n", + pw, + (pw_table - 1)->class_pw, + (pw_table - 1)->class_pw + (pw_table - 1)->max_added_class_pw, + pw_table->class_pw, + pw_table->class_pw + pw_table->max_added_class_pw); + return -ERANGE; + } + + msg->data[2] = pw_table->class_cfg_value; + msg->data[3] = (pw - pw_table->class_pw) / 100; + return 0; + } + + pw_table--; + dev_warn(dev, + "Power limit %dmW not supported. Set to highest power limit %dmW\n", + pw, pw_table->class_pw + pw_table->max_added_class_pw); + msg->data[2] = pw_table->class_cfg_value; + msg->data[3] = pw_table->max_added_class_pw / 100; + return 0; +} + +static int +pd692x0_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *pw_limit_ranges) +{ + struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; + const struct pd692x0_class_pw *pw_table; + int i; + + pw_table = pd692x0_class_pw_table; + c33_pw_limit_ranges = kzalloc_objs(*c33_pw_limit_ranges, + PD692X0_CLASS_PW_TABLE_SIZE); + if (!c33_pw_limit_ranges) + return -ENOMEM; + + for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) { + c33_pw_limit_ranges[i].min = pw_table->class_pw; + c33_pw_limit_ranges[i].max = pw_table->class_pw + + pw_table->max_added_class_pw; + } + + pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges; + return i; +} + +static int +pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + if (buf.sub[1]) + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + + priv->admin_state[id] = admin_state->c33_admin_state; + + return 0; +} + +static int +pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + /* Compare Port Status (Communication Protocol Document par. 7.1) */ + if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90) + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22) + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; + else if (buf.sub[0] == 0x12) + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; + else + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + + return 0; +} + +static int +pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + u32 class; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + class = buf.data[3] >> 4; + if (class <= 8) + return class; + + return 0; +} + +static int +pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + return (buf.data[0] << 4 | buf.data[1]) * 100; +} + +static int +pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1) + return -ERANGE; + + /* PSE core priority start at 0 */ + return buf.data[2] - 1; +} + +static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct pd692x0_msg msg, buf = {0}; + struct pd692x0_msg_ver ver = {0}; + int ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SW_VER]; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) { + dev_err(dev, "Failed to get PSE version (%pe)\n", ERR_PTR(ret)); + return ver; + } + + /* Extract version from the message */ + ver.prod = buf.sub[2]; + ver.maj_sw_ver = (buf.data[0] << 8 | buf.data[1]) / 100; + ver.min_sw_ver = ((buf.data[0] << 8 | buf.data[1]) / 10) % 10; + ver.pa_sw_ver = (buf.data[0] << 8 | buf.data[1]) % 10; + ver.param = buf.data[2]; + ver.build = buf.data[3]; + + return ver; +} + +struct pd692x0_manager { + struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS]; + struct device_node *node; + int nports; +}; + +static int +pd692x0_of_get_ports_manager(struct pd692x0_priv *priv, + struct pd692x0_manager *manager, + struct device_node *np) +{ + struct device_node *node; + int ret, nports, i; + + nports = 0; + for_each_child_of_node(np, node) { + u32 port; + + if (!of_node_name_eq(node, "port")) + continue; + + ret = of_property_read_u32(node, "reg", &port); + if (ret) + goto out; + + if (port >= PD692X0_MAX_MANAGER_PORTS || port != nports) { + dev_err(&priv->client->dev, + "wrong number or order of manager ports (%d)\n", + port); + ret = -EINVAL; + goto out; + } + + of_node_get(node); + manager->port_node[port] = node; + nports++; + } + + manager->nports = nports; + return 0; + +out: + for (i = 0; i < nports; i++) { + of_node_put(manager->port_node[i]); + manager->port_node[i] = NULL; + } + of_node_put(node); + return ret; +} + +static int +pd692x0_of_get_managers(struct pd692x0_priv *priv, + struct pd692x0_manager *manager) +{ + struct device_node *managers_node, *node; + int ret, nmanagers, i, j; + + if (!priv->np) + return -EINVAL; + + nmanagers = 0; + managers_node = of_get_child_by_name(priv->np, "managers"); + if (!managers_node) + return -EINVAL; + + for_each_child_of_node(managers_node, node) { + u32 manager_id; + + if (!of_node_name_eq(node, "manager")) + continue; + + ret = of_property_read_u32(node, "reg", &manager_id); + if (ret) + goto out; + + if (manager_id >= PD692X0_MAX_MANAGERS || + manager_id != nmanagers) { + dev_err(&priv->client->dev, + "wrong number or order of managers (%d)\n", + manager_id); + ret = -EINVAL; + goto out; + } + + ret = pd692x0_of_get_ports_manager(priv, &manager[manager_id], + node); + if (ret) + goto out; + + of_node_get(node); + manager[manager_id].node = node; + nmanagers++; + } + + of_node_put(managers_node); + priv->nmanagers = nmanagers; + return 0; + +out: + for (i = 0; i < nmanagers; i++) { + for (j = 0; j < manager[i].nports; j++) { + of_node_put(manager[i].port_node[j]); + manager[i].port_node[j] = NULL; + } + of_node_put(manager[i].node); + manager[i].node = NULL; + } + + of_node_put(node); + of_node_put(managers_node); + return ret; +} + +static const struct regulator_ops dummy_ops; + +static struct regulator_dev * +pd692x0_register_manager_regulator(struct device *dev, char *reg_name, + struct device_node *node) +{ + struct regulator_init_data *rinit_data; + struct regulator_config rconfig = {0}; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + + rinit_data = devm_kzalloc(dev, sizeof(*rinit_data), + GFP_KERNEL); + if (!rinit_data) + return ERR_PTR(-ENOMEM); + + rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL); + if (!rdesc) + return ERR_PTR(-ENOMEM); + + rdesc->name = reg_name; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->ops = &dummy_ops; + rdesc->owner = THIS_MODULE; + + rinit_data->supply_regulator = "vmain"; + + rconfig.dev = dev; + rconfig.init_data = rinit_data; + rconfig.of_node = node; + + rdev = devm_regulator_register(dev, rdesc, &rconfig); + if (IS_ERR(rdev)) { + dev_err_probe(dev, PTR_ERR(rdev), + "Failed to register regulator\n"); + return rdev; + } + + return rdev; +} + +static int +pd692x0_register_managers_regulator(struct pd692x0_priv *priv, + const struct pd692x0_manager *manager) +{ + struct device *dev = &priv->client->dev; + size_t reg_name_len; + int i; + + /* Each regulator name len is dev name + 12 char + + * int max digit number (10) + 1 + */ + reg_name_len = strlen(dev_name(dev)) + 23; + + for (i = 0; i < priv->nmanagers; i++) { + static const char * const regulators[] = { "vaux5", "vaux3p3" }; + struct regulator_dev *rdev; + char *reg_name; + int ret; + + reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL); + if (!reg_name) + return -ENOMEM; + snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i); + rdev = pd692x0_register_manager_regulator(dev, reg_name, + manager[i].node); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + + /* VMAIN is described as main supply for the manager. + * Add other VAUX power supplies and link them to the + * virtual device rdev->dev. + */ + ret = devm_regulator_bulk_get_enable(&rdev->dev, + ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(&rdev->dev, ret, + "Failed to enable regulators\n"); + + priv->manager_reg[i] = rdev; + } + + return 0; +} + +static int +pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id) +{ + struct pd692x0_msg msg, buf; + int ret, pw_mW; + + pw_mW = priv->manager_pw_budget[id] / 1000; + if (!pw_mW) + return 0; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK]; + msg.data[0] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK]; + msg.data[0] = id; + msg.data[1] = pw_mW >> 8; + msg.data[2] = pw_mW & 0xff; + msg.data[3] = buf.sub[2]; + msg.data[4] = buf.data[0]; + msg.data[5] = buf.data[1]; + msg.data[6] = buf.data[2]; + msg.data[7] = buf.data[3]; + return pd692x0_sendrecv_msg(priv, &msg, &buf); +} + +static int +pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv) +{ + int i, ret; + + for (i = 0; i < priv->nmanagers; i++) { + struct regulator *supply = priv->manager_reg[i]->supply; + int pw_budget; + + pw_budget = regulator_get_unclaimed_power_budget(supply); + if (!pw_budget) + /* Do nothing if no power budget */ + continue; + + /* Max power budget per manager */ + if (pw_budget > 6000000) + pw_budget = 6000000; + ret = regulator_request_power_budget(supply, pw_budget); + if (ret < 0) + return ret; + + priv->manager_pw_budget[i] = pw_budget; + } + + return 0; +} + +static int +pd692x0_configure_managers(struct pd692x0_priv *priv) +{ + int i, ret; + + for (i = 0; i < priv->nmanagers; i++) { + ret = pd692x0_conf_manager_power_budget(priv, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static int +pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset, + const struct pd692x0_manager *manager, + int nmanagers, struct pd692x0_matrix *port_matrix) +{ + int i, j, port_cnt; + bool found = false; + + if (!pairset->np) + return 0; + + /* Look on every managers */ + port_cnt = 0; + for (i = 0; i < nmanagers; i++) { + /* Look on every ports of the manager */ + for (j = 0; j < manager[i].nports; j++) { + if (pairset->np == manager[i].port_node[j]) { + found = true; + break; + } + } + port_cnt += j; + + if (found) + break; + } + + if (!found) + return -ENODEV; + + if (pairset->pinout == ALTERNATIVE_A) + port_matrix->hw_port_a = port_cnt; + else if (pairset->pinout == ALTERNATIVE_B) + port_matrix->hw_port_b = port_cnt; + + return 0; +} + +static int +pd692x0_set_ports_matrix(struct pd692x0_priv *priv, + const struct pd692x0_manager *manager) +{ + struct pd692x0_matrix *port_matrix = priv->port_matrix; + struct pse_controller_dev *pcdev = &priv->pcdev; + int i, ret; + + /* Init Matrix */ + for (i = 0; i < PD692X0_MAX_PIS; i++) { + port_matrix[i].hw_port_a = 0xff; + port_matrix[i].hw_port_b = 0xff; + } + + /* Update with values for every PSE PIs */ + for (i = 0; i < pcdev->nr_lines; i++) { + ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0], + manager, priv->nmanagers, + &port_matrix[i]); + if (ret) { + dev_err(&priv->client->dev, + "unable to configure pi %d pairset 0", i); + return ret; + } + + ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1], + manager, priv->nmanagers, + &port_matrix[i]); + if (ret) { + dev_err(&priv->client->dev, + "unable to configure pi %d pairset 1", i); + return ret; + } + } + + return 0; +} + +static int +pd692x0_write_ports_matrix(struct pd692x0_priv *priv) +{ + struct pd692x0_matrix *port_matrix = priv->port_matrix; + struct pd692x0_msg msg, buf; + int ret, i; + + /* Write temporary Matrix */ + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_TMP_PORT_MATRIX]; + for (i = 0; i < PD692X0_MAX_PIS; i++) { + msg.sub[2] = i; + msg.data[0] = port_matrix[i].hw_port_b; + msg.data[1] = port_matrix[i].hw_port_a; + + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + } + + /* Program Matrix */ + msg = pd692x0_msg_template_list[PD692X0_MSG_PRG_PORT_MATRIX]; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + return 0; +} + +static int pd692x0_hw_conf_init(struct pd692x0_priv *priv) +{ + int ret; + + /* Is PD692x0 ready to be configured? */ + if (priv->fw_state != PD692X0_FW_OK && + priv->fw_state != PD692X0_FW_COMPLETE) + return 0; + + ret = pd692x0_configure_managers(priv); + if (ret) + return ret; + + ret = pd692x0_write_ports_matrix(priv); + if (ret) + return ret; + + return 0; +} + +static void pd692x0_of_put_managers(struct pd692x0_priv *priv, + struct pd692x0_manager *manager) +{ + int i, j; + + for (i = 0; i < priv->nmanagers; i++) { + for (j = 0; j < manager[i].nports; j++) + of_node_put(manager[i].port_node[j]); + of_node_put(manager[i].node); + } +} + +static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv) +{ + int i; + + for (i = 0; i < PD692X0_MAX_MANAGERS; i++) { + struct regulator *supply; + + if (!priv->manager_reg[i] || !priv->manager_pw_budget[i]) + continue; + + supply = priv->manager_reg[i]->supply; + if (!supply) + continue; + + regulator_free_power_budget(supply, + priv->manager_pw_budget[i]); + } +} + +static int +pd692x0_save_user_byte(struct pd692x0_priv *priv) +{ + struct pd692x0_msg msg, buf; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE]; + return pd692x0_sendrecv_msg(priv, &msg, &buf); +} + +static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_matrix *port_matrix; + struct pd692x0_manager *manager; + int ret; + + manager = kzalloc_objs(*manager, PD692X0_MAX_MANAGERS); + if (!manager) + return -ENOMEM; + + port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS, + sizeof(*port_matrix), GFP_KERNEL); + if (!port_matrix) { + ret = -ENOMEM; + goto err_free_manager; + } + priv->port_matrix = port_matrix; + + ret = pd692x0_of_get_managers(priv, manager); + if (ret < 0) + goto err_free_manager; + + ret = pd692x0_register_managers_regulator(priv, manager); + if (ret) + goto err_of_managers; + + ret = pd692x0_req_managers_pw_budget(priv); + if (ret) + goto err_of_managers; + + ret = pd692x0_set_ports_matrix(priv, manager); + if (ret) + goto err_managers_req_pw; + + /* Do not init the conf if it is already saved */ + if (!priv->cfg_saved) { + ret = pd692x0_hw_conf_init(priv); + if (ret) + goto err_managers_req_pw; + + ret = pd692x0_save_user_byte(priv); + if (ret) + goto err_managers_req_pw; + } + + pd692x0_of_put_managers(priv, manager); + kfree(manager); + return 0; + +err_managers_req_pw: + pd692x0_managers_free_pw_budget(priv); +err_of_managers: + pd692x0_of_put_managers(priv, manager); +err_free_manager: + kfree(manager); + return ret; +} + +static int pd692x0_pi_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_MEAS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + /* Convert 0.1V unit to uV */ + return (buf.sub[0] << 8 | buf.sub[1]) * 100000; +} + +static int pd692x0_pi_get_pw_limit(struct pse_controller_dev *pcdev, + int id) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + + return pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]); +} + +static int pd692x0_pi_set_pw_limit(struct pse_controller_dev *pcdev, + int id, int max_mW) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct device *dev = &priv->client->dev; + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; + msg.sub[2] = id; + ret = pd692x0_pi_set_pw_from_table(dev, &msg, max_mW); + if (ret) + return ret; + + return pd692x0_sendrecv_msg(priv, &msg, &buf); +} + +static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id, + unsigned int prio) +{ + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; + int ret; + + ret = pd692x0_fw_unavailable(priv); + if (ret) + return ret; + + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; + msg.sub[2] = id; + /* Controller priority from 1 to 3 */ + msg.data[4] = prio + 1; + + return pd692x0_sendrecv_msg(priv, &msg, &buf); +} + +static const struct pse_controller_ops pd692x0_ops = { + .setup_pi_matrix = pd692x0_setup_pi_matrix, + .pi_get_admin_state = pd692x0_pi_get_admin_state, + .pi_get_pw_status = pd692x0_pi_get_pw_status, + .pi_get_ext_state = pd692x0_pi_get_ext_state, + .pi_get_pw_class = pd692x0_pi_get_pw_class, + .pi_get_actual_pw = pd692x0_pi_get_actual_pw, + .pi_enable = pd692x0_pi_enable, + .pi_disable = pd692x0_pi_disable, + .pi_get_voltage = pd692x0_pi_get_voltage, + .pi_get_pw_limit = pd692x0_pi_get_pw_limit, + .pi_set_pw_limit = pd692x0_pi_set_pw_limit, + .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges, + .pi_get_prio = pd692x0_pi_get_prio, + .pi_set_prio = pd692x0_pi_set_prio, +}; + +#define PD692X0_FW_LINE_MAX_SZ 0xff +static int pd692x0_fw_get_next_line(const u8 *data, + char *line, size_t size) +{ + size_t line_size; + int i; + + line_size = min_t(size_t, size, PD692X0_FW_LINE_MAX_SZ); + + memset(line, 0, PD692X0_FW_LINE_MAX_SZ); + for (i = 0; i < line_size - 1; i++) { + if (*data == '\r' && *(data + 1) == '\n') { + line[i] = '\r'; + line[i + 1] = '\n'; + return i + 2; + } + line[i] = *data; + data++; + } + + return -EIO; +} + +static enum fw_upload_err +pd692x0_fw_recv_resp(const struct i2c_client *client, unsigned long ms_timeout, + const char *msg_ok, unsigned int msg_size) +{ + /* Maximum controller response size */ + char fw_msg_buf[5] = {0}; + unsigned long timeout; + int ret; + + if (msg_size > sizeof(fw_msg_buf)) + return FW_UPLOAD_ERR_RW_ERROR; + + /* Read until we get something */ + timeout = msecs_to_jiffies(ms_timeout) + jiffies; + while (true) { + if (time_is_before_jiffies(timeout)) + return FW_UPLOAD_ERR_TIMEOUT; + + ret = i2c_master_recv(client, fw_msg_buf, 1); + if (ret < 0 || *fw_msg_buf == 0) { + usleep_range(1000, 2000); + continue; + } else { + break; + } + } + + /* Read remaining characters */ + ret = i2c_master_recv(client, fw_msg_buf + 1, msg_size - 1); + if (strncmp(fw_msg_buf, msg_ok, msg_size)) { + dev_err(&client->dev, + "Wrong FW download process answer (%*pE)\n", + msg_size, fw_msg_buf); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +static int pd692x0_fw_write_line(const struct i2c_client *client, + const char line[PD692X0_FW_LINE_MAX_SZ], + const bool last_line) +{ + int ret; + + while (*line != 0) { + ret = i2c_master_send(client, line, 1); + if (ret < 0) + return FW_UPLOAD_ERR_RW_ERROR; + line++; + } + + if (last_line) { + ret = pd692x0_fw_recv_resp(client, 100, "TP\r\n", + sizeof("TP\r\n") - 1); + if (ret) + return ret; + } else { + ret = pd692x0_fw_recv_resp(client, 100, "T*\r\n", + sizeof("T*\r\n") - 1); + if (ret) + return ret; + } + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err pd692x0_fw_reset(const struct i2c_client *client) +{ + const struct pd692x0_msg zero = {0}; + struct pd692x0_msg buf = {0}; + unsigned long timeout; + char cmd[] = "RST"; + int ret; + + ret = i2c_master_send(client, cmd, strlen(cmd)); + if (ret < 0) { + dev_err(&client->dev, + "Failed to reset the controller (%pe)\n", + ERR_PTR(ret)); + return ret; + } + + timeout = msecs_to_jiffies(10000) + jiffies; + while (true) { + if (time_is_before_jiffies(timeout)) + return FW_UPLOAD_ERR_TIMEOUT; + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret < 0 || + !memcmp(&buf, &zero, sizeof(buf))) + usleep_range(1000, 2000); + else + break; + } + + /* Is the reply a successful report message */ + if (buf.key != PD692X0_KEY_TLM || buf.echo != 0xff || + buf.sub[0] & 0x01) { + dev_err(&client->dev, "PSE controller error\n"); + return FW_UPLOAD_ERR_HW_ERROR; + } + + /* Is the firmware operational */ + if (buf.sub[0] & 0x02) { + dev_err(&client->dev, + "PSE firmware error. Please update it.\n"); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err pd692x0_fw_prepare(struct fw_upload *fwl, + const u8 *data, u32 size) +{ + struct pd692x0_priv *priv = fwl->dd_handle; + const struct i2c_client *client = priv->client; + enum pd692x0_fw_state last_fw_state; + int ret; + + priv->cancel_request = false; + last_fw_state = priv->fw_state; + + priv->fw_state = PD692X0_FW_PREPARE; + + /* Enter program mode */ + if (last_fw_state == PD692X0_FW_BROKEN) { + const char *msg = "ENTR"; + const char *c; + + c = msg; + do { + ret = i2c_master_send(client, c, 1); + if (ret < 0) + return FW_UPLOAD_ERR_RW_ERROR; + if (*(c + 1)) + usleep_range(10000, 20000); + } while (*(++c)); + } else { + struct pd692x0_msg msg, buf; + + msg = pd692x0_msg_template_list[PD692X0_MSG_DOWNLOAD_CMD]; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enter programming mode (%pe)\n", + ERR_PTR(ret)); + return FW_UPLOAD_ERR_RW_ERROR; + } + } + + ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1); + if (ret) + goto err_out; + + if (priv->cancel_request) { + ret = FW_UPLOAD_ERR_CANCELED; + goto err_out; + } + + return FW_UPLOAD_ERR_NONE; + +err_out: + pd692x0_fw_reset(priv->client); + priv->fw_state = last_fw_state; + return ret; +} + +static enum fw_upload_err pd692x0_fw_write(struct fw_upload *fwl, + const u8 *data, u32 offset, + u32 size, u32 *written) +{ + struct pd692x0_priv *priv = fwl->dd_handle; + char line[PD692X0_FW_LINE_MAX_SZ]; + const struct i2c_client *client; + int ret, i; + char cmd; + + client = priv->client; + priv->fw_state = PD692X0_FW_WRITE; + + /* Erase */ + cmd = 'E'; + ret = i2c_master_send(client, &cmd, 1); + if (ret < 0) { + dev_err(&client->dev, + "Failed to boot programming mode (%pe)\n", + ERR_PTR(ret)); + return FW_UPLOAD_ERR_RW_ERROR; + } + + ret = pd692x0_fw_recv_resp(client, 100, "TOE\r\n", sizeof("TOE\r\n") - 1); + if (ret) + return ret; + + ret = pd692x0_fw_recv_resp(client, 5000, "TE\r\n", sizeof("TE\r\n") - 1); + if (ret) + dev_warn(&client->dev, + "Failed to erase internal memory, however still try to write Firmware\n"); + + ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1); + if (ret) + dev_warn(&client->dev, + "Failed to erase internal memory, however still try to write Firmware\n"); + + if (priv->cancel_request) + return FW_UPLOAD_ERR_CANCELED; + + /* Program */ + cmd = 'P'; + ret = i2c_master_send(client, &cmd, sizeof(char)); + if (ret < 0) { + dev_err(&client->dev, + "Failed to boot programming mode (%pe)\n", + ERR_PTR(ret)); + return ret; + } + + ret = pd692x0_fw_recv_resp(client, 100, "TOP\r\n", sizeof("TOP\r\n") - 1); + if (ret) + return ret; + + i = 0; + while (i < size) { + ret = pd692x0_fw_get_next_line(data, line, size - i); + if (ret < 0) { + ret = FW_UPLOAD_ERR_FW_INVALID; + goto err; + } + + i += ret; + data += ret; + if (line[0] == 'S' && line[1] == '0') { + continue; + } else if (line[0] == 'S' && line[1] == '7') { + ret = pd692x0_fw_write_line(client, line, true); + if (ret) + goto err; + } else { + ret = pd692x0_fw_write_line(client, line, false); + if (ret) + goto err; + } + + if (priv->cancel_request) { + ret = FW_UPLOAD_ERR_CANCELED; + goto err; + } + } + *written = i; + + msleep(400); + + return FW_UPLOAD_ERR_NONE; + +err: + strscpy_pad(line, "S7\r\n", sizeof(line)); + pd692x0_fw_write_line(client, line, true); + return ret; +} + +static enum fw_upload_err pd692x0_fw_poll_complete(struct fw_upload *fwl) +{ + struct pd692x0_priv *priv = fwl->dd_handle; + const struct i2c_client *client = priv->client; + struct pd692x0_msg_ver ver; + int ret; + + priv->fw_state = PD692X0_FW_COMPLETE; + + ret = pd692x0_fw_reset(client); + if (ret) + return ret; + + ver = pd692x0_get_sw_version(priv); + if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) { + dev_err(&client->dev, + "Too old firmware version. Please update it\n"); + priv->fw_state = PD692X0_FW_NEED_UPDATE; + return FW_UPLOAD_ERR_FW_INVALID; + } + + ret = pd692x0_hw_conf_init(priv); + if (ret < 0) { + dev_err(&client->dev, "Error configuring ports matrix (%pe)\n", + ERR_PTR(ret)); + priv->fw_state = PD692X0_FW_NEED_UPDATE; + return FW_UPLOAD_ERR_HW_ERROR; + } + + priv->fw_state = PD692X0_FW_OK; + return FW_UPLOAD_ERR_NONE; +} + +static void pd692x0_fw_cancel(struct fw_upload *fwl) +{ + struct pd692x0_priv *priv = fwl->dd_handle; + + priv->cancel_request = true; +} + +static void pd692x0_fw_cleanup(struct fw_upload *fwl) +{ + struct pd692x0_priv *priv = fwl->dd_handle; + + switch (priv->fw_state) { + case PD692X0_FW_WRITE: + pd692x0_fw_reset(priv->client); + fallthrough; + case PD692X0_FW_COMPLETE: + priv->fw_state = PD692X0_FW_BROKEN; + break; + default: + break; + } +} + +static const struct fw_upload_ops pd692x0_fw_ops = { + .prepare = pd692x0_fw_prepare, + .write = pd692x0_fw_write, + .poll_complete = pd692x0_fw_poll_complete, + .cancel = pd692x0_fw_cancel, + .cleanup = pd692x0_fw_cleanup, +}; + +static int pd692x0_i2c_probe(struct i2c_client *client) +{ + static const char * const regulators[] = { "vdd", "vdda" }; + struct pd692x0_msg msg, buf = {0}, zero = {0}; + struct device *dev = &client->dev; + struct gpio_desc *disable_ports; + struct pd692x0_msg_ver ver; + struct pd692x0_priv *priv; + struct fw_upload *fwl; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable regulators\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "i2c check functionality failed\n"); + return -ENXIO; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + disable_ports = devm_gpiod_get_optional(dev, "disable-ports", GPIOD_OUT_LOW); + if (IS_ERR(disable_ports)) + return dev_err_probe(&client->dev, PTR_ERR(disable_ports), + "Failed to get disable ports GPIO\n"); + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret != sizeof(buf)) { + dev_err(dev, "Failed to get device status\n"); + return -EIO; + } + + /* Probe has been already run and the status dumped */ + if (!memcmp(&buf, &zero, sizeof(buf))) { + /* Ask again the controller status */ + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SYS_STATUS]; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) { + dev_err(dev, "Failed to get device status\n"); + return ret; + } + } + + if (buf.key != 0x03 || buf.sub[0] & 0x01) { + dev_err(dev, "PSE controller error\n"); + return -EIO; + } + if (buf.sub[0] & 0x02) { + dev_err(dev, "PSE firmware error. Please update it.\n"); + priv->fw_state = PD692X0_FW_BROKEN; + } else { + ver = pd692x0_get_sw_version(priv); + dev_info(&client->dev, "Software version %d.%02d.%d.%d\n", + ver.prod, ver.maj_sw_ver, ver.min_sw_ver, + ver.pa_sw_ver); + + if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) { + dev_err(dev, "Too old firmware version. Please update it\n"); + priv->fw_state = PD692X0_FW_NEED_UPDATE; + } else { + priv->fw_state = PD692X0_FW_OK; + } + } + + if (buf.data[2] == PD692X0_USER_BYTE) + priv->cfg_saved = true; + + priv->np = dev->of_node; + priv->pcdev.nr_lines = PD692X0_MAX_PIS; + priv->pcdev.owner = THIS_MODULE; + priv->pcdev.ops = &pd692x0_ops; + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_C33; + priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC; + priv->pcdev.pis_prio_max = 2; + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) + return dev_err_probe(dev, ret, + "failed to register PSE controller\n"); + + fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev), + &pd692x0_fw_ops, priv); + if (IS_ERR(fwl)) + return dev_err_probe(dev, PTR_ERR(fwl), + "failed to register to the Firmware Upload API\n"); + priv->fwl = fwl; + + return 0; +} + +static void pd692x0_i2c_remove(struct i2c_client *client) +{ + struct pd692x0_priv *priv = i2c_get_clientdata(client); + + pd692x0_managers_free_pw_budget(priv); + firmware_upload_unregister(priv->fwl); +} + +static const struct i2c_device_id pd692x0_id[] = { + { .name = PD692X0_PSE_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pd692x0_id); + +static const struct of_device_id pd692x0_of_match[] = { + { .compatible = "microchip,pd69200", }, + { .compatible = "microchip,pd69210", }, + { .compatible = "microchip,pd69220", }, + { }, +}; +MODULE_DEVICE_TABLE(of, pd692x0_of_match); + +static struct i2c_driver pd692x0_driver = { + .probe = pd692x0_i2c_probe, + .remove = pd692x0_i2c_remove, + .id_table = pd692x0_id, + .driver = { + .name = PD692X0_PSE_NAME, + .of_match_table = pd692x0_of_match, + }, +}; +module_i2c_driver(pd692x0_driver); + +MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>"); +MODULE_DESCRIPTION("Microchip PD692x0 PoE PSE Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c new file mode 100644 index 000000000..a5e6d7b26 --- /dev/null +++ b/drivers/net/pse-pd/pse_core.c @@ -0,0 +1,1983 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Framework for Ethernet Power Sourcing Equipment +// +// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> +// + +#include <linux/device.h> +#include <linux/ethtool.h> +#include <linux/ethtool_netlink.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/pse-pd/pse.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/rtnetlink.h> +#include <net/net_trackers.h> + +#define PSE_PW_D_LIMIT INT_MAX + +static DEFINE_MUTEX(pse_list_mutex); +static LIST_HEAD(pse_controller_list); +static DEFINE_XARRAY_ALLOC(pse_pw_d_map); +static DEFINE_MUTEX(pse_pw_d_mutex); + +/** + * struct pse_control - a PSE control + * @pcdev: a pointer to the PSE controller device + * this PSE control belongs to + * @ps: PSE PI supply of the PSE control + * @list: list entry for the pcdev's PSE controller list + * @id: ID of the PSE line in the PSE controller device + * @refcnt: Number of gets of this pse_control + * @attached_phydev: PHY device pointer attached by the PSE control + */ +struct pse_control { + struct pse_controller_dev *pcdev; + struct regulator *ps; + struct list_head list; + unsigned int id; + struct kref refcnt; + struct phy_device *attached_phydev; +}; + +/** + * struct pse_power_domain - a PSE power domain + * @id: ID of the power domain + * @supply: Power supply the Power Domain + * @refcnt: Number of gets of this pse_power_domain + * @budget_eval_strategy: Current power budget evaluation strategy of the + * power domain + */ +struct pse_power_domain { + int id; + struct regulator *supply; + struct kref refcnt; + u32 budget_eval_strategy; +}; + +static int of_load_single_pse_pi_pairset(struct device_node *node, + struct pse_pi *pi, + int pairset_num) +{ + struct device_node *pairset_np; + const char *name; + int ret; + + ret = of_property_read_string_index(node, "pairset-names", + pairset_num, &name); + if (ret) + return ret; + + if (!strcmp(name, "alternative-a")) { + pi->pairset[pairset_num].pinout = ALTERNATIVE_A; + } else if (!strcmp(name, "alternative-b")) { + pi->pairset[pairset_num].pinout = ALTERNATIVE_B; + } else { + pr_err("pse: wrong pairset-names value %s (%pOF)\n", + name, node); + return -EINVAL; + } + + pairset_np = of_parse_phandle(node, "pairsets", pairset_num); + if (!pairset_np) + return -ENODEV; + + pi->pairset[pairset_num].np = pairset_np; + + return 0; +} + +/** + * of_load_pse_pi_pairsets - load PSE PI pairsets pinout and polarity + * @node: a pointer of the device node + * @pi: a pointer of the PSE PI to fill + * @npairsets: the number of pairsets (1 or 2) used by the PI + * + * Return: 0 on success and failure value on error + */ +static int of_load_pse_pi_pairsets(struct device_node *node, + struct pse_pi *pi, + int npairsets) +{ + int i, ret; + + ret = of_property_count_strings(node, "pairset-names"); + if (ret != npairsets) { + pr_err("pse: amount of pairsets and pairset-names is not equal %d != %d (%pOF)\n", + npairsets, ret, node); + return -EINVAL; + } + + for (i = 0; i < npairsets; i++) { + ret = of_load_single_pse_pi_pairset(node, pi, i); + if (ret) + goto out; + } + + if (npairsets == 2 && + pi->pairset[0].pinout == pi->pairset[1].pinout) { + pr_err("pse: two PI pairsets can not have identical pinout (%pOF)", + node); + ret = -EINVAL; + } + +out: + /* If an error appears, release all the pairset device node kref */ + if (ret) { + of_node_put(pi->pairset[0].np); + pi->pairset[0].np = NULL; + of_node_put(pi->pairset[1].np); + pi->pairset[1].np = NULL; + } + + return ret; +} + +static void pse_release_pis(struct pse_controller_dev *pcdev) +{ + int i; + + for (i = 0; i < pcdev->nr_lines; i++) { + of_node_put(pcdev->pi[i].pairset[0].np); + of_node_put(pcdev->pi[i].pairset[1].np); + of_node_put(pcdev->pi[i].np); + } + kfree(pcdev->pi); +} + +/** + * of_load_pse_pis - load all the PSE PIs + * @pcdev: a pointer to the PSE controller device + * + * Return: 0 on success and failure value on error + */ +static int of_load_pse_pis(struct pse_controller_dev *pcdev) +{ + struct device_node *np = pcdev->dev->of_node; + struct device_node *node, *pis; + int ret; + + if (!np) + return -ENODEV; + + pcdev->pi = kzalloc_objs(*pcdev->pi, pcdev->nr_lines); + if (!pcdev->pi) + return -ENOMEM; + + pis = of_get_child_by_name(np, "pse-pis"); + if (!pis) { + /* no description of PSE PIs */ + pcdev->no_of_pse_pi = true; + return 0; + } + + for_each_child_of_node(pis, node) { + struct pse_pi pi = {0}; + u32 id; + + if (!of_node_name_eq(node, "pse-pi")) + continue; + + ret = of_property_read_u32(node, "reg", &id); + if (ret) { + dev_err(pcdev->dev, + "can't get reg property for node '%pOF'", + node); + goto out; + } + + if (id >= pcdev->nr_lines) { + dev_err(pcdev->dev, + "reg value (%u) is out of range (%u) (%pOF)\n", + id, pcdev->nr_lines, node); + ret = -EINVAL; + goto out; + } + + if (pcdev->pi[id].np) { + dev_err(pcdev->dev, + "other node with same reg value was already registered. %pOF : %pOF\n", + pcdev->pi[id].np, node); + ret = -EINVAL; + goto out; + } + + ret = of_count_phandle_with_args(node, "pairsets", NULL); + /* npairsets is limited to value one or two */ + if (ret == 1 || ret == 2) { + ret = of_load_pse_pi_pairsets(node, &pi, ret); + if (ret) + goto out; + } else if (ret != -ENOENT) { + dev_err(pcdev->dev, + "error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n", + ret, node); + ret = -EINVAL; + goto out; + } + + of_node_get(node); + pi.np = node; + memcpy(&pcdev->pi[id], &pi, sizeof(pi)); + } + + of_node_put(pis); + return 0; + +out: + pse_release_pis(pcdev); + of_node_put(node); + of_node_put(pis); + return ret; +} + +/** + * pse_control_find_by_id - Find pse_control from an id + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * + * Return: pse_control pointer or NULL. The device returned has had a + * reference added and the pointer is safe until the user calls + * pse_control_put() to indicate they have finished with it. + */ +static struct pse_control * +pse_control_find_by_id(struct pse_controller_dev *pcdev, int id) +{ + struct pse_control *psec; + + mutex_lock(&pse_list_mutex); + list_for_each_entry(psec, &pcdev->pse_control_head, list) { + if (psec->id == id) { + kref_get(&psec->refcnt); + mutex_unlock(&pse_list_mutex); + return psec; + } + } + mutex_unlock(&pse_list_mutex); + return NULL; +} + +/** + * pse_control_get_netdev - Return netdev associated to a PSE control + * @psec: PSE control pointer + * + * Return: netdev pointer or NULL + */ +static struct net_device *pse_control_get_netdev(struct pse_control *psec) +{ + ASSERT_RTNL(); + + if (!psec || !psec->attached_phydev) + return NULL; + + return psec->attached_phydev->attached_dev; +} + +/** + * pse_pi_is_hw_enabled - Is PI enabled at the hardware level + * @pcdev: a pointer to the PSE controller device + * @id: Index of the PI + * + * Return: 1 if the PI is enabled at the hardware level, 0 if not, and + * a failure value on error + */ +static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id) +{ + struct pse_admin_state admin_state = {0}; + int ret; + + ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state); + if (ret < 0) + return ret; + + /* PI is well enabled at the hardware level */ + if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || + admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) + return 1; + + return 0; +} + +/** + * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state + * which mean the power is not yet being + * delivered + * @pcdev: a pointer to the PSE controller device + * @id: Index of the PI + * + * Detects if a PI is enabled in software with a PD detected, but the hardware + * admin state hasn't been applied yet. + * + * This function is used in the power delivery and retry mechanisms to determine + * which PIs need to have power delivery attempted again. + * + * Return: true if the PI has admin enable flag set in software but not yet + * reflected in the hardware admin state, false otherwise. + */ +static bool +pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id) +{ + int ret; + + /* PI not enabled or nothing is plugged */ + if (!pcdev->pi[id].admin_state_enabled || + !pcdev->pi[id].isr_pd_detected) + return false; + + ret = pse_pi_is_hw_enabled(pcdev, id); + /* PSE PI is already enabled at hardware level */ + if (ret == 1) + return false; + + return true; +} + +static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, + int id, + struct netlink_ext_ack *extack); + +/** + * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a + * PSE power domain + * @pcdev: a pointer to the PSE controller device + * @pw_d: a pointer to the PSE power domain + * + * Scans all ports in the specified power domain and attempts to enable power + * delivery to any ports that have admin enable state set but don't yet have + * hardware power enabled. Used when there are changes in connection status, + * admin state, or priority that might allow previously unpowered ports to + * receive power, especially in over-budget conditions. + */ +static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d) +{ + int i, ret = 0; + + for (i = 0; i < pcdev->nr_lines; i++) { + int prio_max = pcdev->nr_lines; + struct netlink_ext_ack extack; + + if (pcdev->pi[i].pw_d != pw_d) + continue; + + if (!pse_pi_is_admin_enable_pending(pcdev, i)) + continue; + + /* Do not try to enable PI with a lower prio (higher value) + * than one which already can't be enabled. + */ + if (pcdev->pi[i].prio > prio_max) + continue; + + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack); + if (ret == -ERANGE) + prio_max = pcdev->pi[i].prio; + } +} + +/** + * pse_pw_d_is_sw_pw_control - Determine if power control is software managed + * @pcdev: a pointer to the PSE controller device + * @pw_d: a pointer to the PSE power domain + * + * This function determines whether the power control for a specific power + * domain is managed by software in the interrupt handler rather than directly + * by hardware. + * + * Software power control is active in the following cases: + * - When the budget evaluation strategy is set to static + * - When the budget evaluation strategy is disabled but the PSE controller + * has an interrupt handler that can report if a Powered Device is connected + * + * Return: true if the power control of the power domain is managed by software, + * false otherwise + */ +static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d) +{ + if (!pw_d) + return false; + + if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) + return true; + if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED && + pcdev->ops->pi_enable && pcdev->irq) + return true; + + return false; +} + +static int pse_pi_is_enabled(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + const struct pse_controller_ops *ops; + int id, ret; + + ops = pcdev->ops; + if (!ops->pi_get_admin_state) + return -EOPNOTSUPP; + + id = rdev_get_id(rdev); + mutex_lock(&pcdev->lock); + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) { + ret = pcdev->pi[id].admin_state_enabled; + goto out; + } + + ret = pse_pi_is_hw_enabled(pcdev, id); + +out: + mutex_unlock(&pcdev->lock); + + return ret; +} + +/** + * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI + * @pi: a pointer to the PSE PI + */ +static void pse_pi_deallocate_pw_budget(struct pse_pi *pi) +{ + if (!pi->pw_d || !pi->pw_allocated_mW) + return; + + regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW); + pi->pw_allocated_mW = 0; +} + +/** + * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been + * acquired. + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * + * Return: 0 on success and failure value on error + */ +static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + const struct pse_controller_ops *ops = pcdev->ops; + int ret; + + if (!ops->pi_disable) + return -EOPNOTSUPP; + + ret = ops->pi_disable(pcdev, id); + if (ret) + return ret; + + pse_pi_deallocate_pw_budget(&pcdev->pi[id]); + + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) + pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d); + + return 0; +} + +/** + * pse_disable_pi_pol - Disable a PI on a power budget policy + * @pcdev: a pointer to the PSE + * @id: index of the PSE PI + * + * Return: 0 on success and failure value on error + */ +static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id) +{ + unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET; + struct pse_ntf ntf = {}; + int ret; + + dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id); + + ret = _pse_pi_disable(pcdev, id); + if (ret) + notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; + + ntf.notifs = notifs; + ntf.id = id; + kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock); + schedule_work(&pcdev->ntf_work); + + return ret; +} + +/** + * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE + * power domain + * @pcdev: a pointer to the PSE + * @pw_d: a pointer to the PSE power domain + * @prio: priority + * + * Return: 0 on success and failure value on error + */ +static int pse_disable_pi_prio(struct pse_controller_dev *pcdev, + struct pse_power_domain *pw_d, + int prio) +{ + int i; + + for (i = 0; i < pcdev->nr_lines; i++) { + int ret; + + if (pcdev->pi[i].prio != prio || + pcdev->pi[i].pw_d != pw_d || + pse_pi_is_hw_enabled(pcdev, i) <= 0) + continue; + + ret = pse_disable_pi_pol(pcdev, i); + if (ret) + return ret; + } + + return 0; +} + +/** + * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI + * when the budget eval strategy is + * static + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @pw_req: power requested in mW + * @extack: extack for error reporting + * + * Allocates power using static budget evaluation strategy, where allocation + * is based on PD classification. When insufficient budget is available, + * lower-priority ports (higher priority numbers) are turned off first. + * + * Return: 0 on success and failure value on error + */ +static int +pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id, + int pw_req, struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + int ret, _prio; + + _prio = pcdev->nr_lines; + while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) { + if (_prio <= pi->prio) { + NL_SET_ERR_MSG_FMT(extack, + "PI %d: not enough power budget available", + id); + return -ERANGE; + } + + ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio); + if (ret < 0) + return ret; + + _prio--; + } + + pi->pw_allocated_mW = pw_req; + return 0; +} + +/** + * pse_pi_allocate_pw_budget - Allocate power budget for the PI + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @pw_req: power requested in mW + * @extack: extack for error reporting + * + * Return: 0 on success and failure value on error + */ +static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id, + int pw_req, struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + + if (!pi->pw_d) + return 0; + + /* PSE_BUDGET_EVAL_STRAT_STATIC */ + if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC) + return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req, + extack); + + return 0; +} + +/** + * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power + * control. Assumes the PSE lock has been + * acquired. + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @extack: extack for error reporting + * + * Return: 0 on success and failure value on error + */ +static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev, + int id, + struct netlink_ext_ack *extack) +{ + const struct pse_controller_ops *ops = pcdev->ops; + struct pse_pi *pi = &pcdev->pi[id]; + int ret, pw_req; + + if (!ops->pi_get_pw_req) { + /* No power allocation management */ + ret = ops->pi_enable(pcdev, id); + if (ret) + NL_SET_ERR_MSG_FMT(extack, + "PI %d: enable error %d", + id, ret); + return ret; + } + + ret = ops->pi_get_pw_req(pcdev, id); + if (ret < 0) + return ret; + + pw_req = ret; + + /* Compare requested power with port power limit and use the lowest + * one. + */ + if (ops->pi_get_pw_limit) { + ret = ops->pi_get_pw_limit(pcdev, id); + if (ret < 0) + return ret; + + if (ret < pw_req) + pw_req = ret; + } + + ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack); + if (ret) + return ret; + + ret = ops->pi_enable(pcdev, id); + if (ret) { + pse_pi_deallocate_pw_budget(pi); + NL_SET_ERR_MSG_FMT(extack, + "PI %d: enable error %d", + id, ret); + return ret; + } + + return 0; +} + +static int pse_pi_enable(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + const struct pse_controller_ops *ops; + int id, ret = 0; + + ops = pcdev->ops; + if (!ops->pi_enable) + return -EOPNOTSUPP; + + id = rdev_get_id(rdev); + mutex_lock(&pcdev->lock); + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) { + /* Manage enabled status by software. + * Real enable process will happen if a port is connected. + */ + if (pcdev->pi[id].isr_pd_detected) { + struct netlink_ext_ack extack; + + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack); + } + if (!ret || ret == -ERANGE) { + pcdev->pi[id].admin_state_enabled = 1; + ret = 0; + } + mutex_unlock(&pcdev->lock); + return ret; + } + + ret = ops->pi_enable(pcdev, id); + if (!ret) + pcdev->pi[id].admin_state_enabled = 1; + mutex_unlock(&pcdev->lock); + + return ret; +} + +static int pse_pi_disable(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + struct pse_pi *pi; + int id, ret; + + id = rdev_get_id(rdev); + pi = &pcdev->pi[id]; + mutex_lock(&pcdev->lock); + ret = _pse_pi_disable(pcdev, id); + if (!ret) + pi->admin_state_enabled = 0; + + mutex_unlock(&pcdev->lock); + return 0; +} + +static int _pse_pi_get_voltage(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + const struct pse_controller_ops *ops; + int id; + + ops = pcdev->ops; + if (!ops->pi_get_voltage) + return -EOPNOTSUPP; + + id = rdev_get_id(rdev); + return ops->pi_get_voltage(pcdev, id); +} + +static int pse_pi_get_voltage(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + int ret; + + mutex_lock(&pcdev->lock); + ret = _pse_pi_get_voltage(rdev); + mutex_unlock(&pcdev->lock); + + return ret; +} + +static int pse_pi_get_current_limit(struct regulator_dev *rdev) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + const struct pse_controller_ops *ops; + int id, uV, mW, ret; + s64 tmp_64; + + ops = pcdev->ops; + id = rdev_get_id(rdev); + if (!ops->pi_get_pw_limit || !ops->pi_get_voltage) + return -EOPNOTSUPP; + + mutex_lock(&pcdev->lock); + ret = ops->pi_get_pw_limit(pcdev, id); + if (ret < 0) + goto out; + mW = ret; + + ret = _pse_pi_get_voltage(rdev); + if (!ret) { + dev_err(pcdev->dev, "Voltage null\n"); + ret = -ERANGE; + goto out; + } + if (ret < 0) + goto out; + uV = ret; + + tmp_64 = mW; + tmp_64 *= 1000000000ull; + /* uA = mW * 1000000000 / uV */ + ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); + +out: + mutex_unlock(&pcdev->lock); + return ret; +} + +static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, + int max_uA) +{ + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); + const struct pse_controller_ops *ops; + int id, mW, ret; + s64 tmp_64; + + ops = pcdev->ops; + if (!ops->pi_set_pw_limit || !ops->pi_get_voltage) + return -EOPNOTSUPP; + + if (max_uA > MAX_PI_CURRENT) + return -ERANGE; + + id = rdev_get_id(rdev); + mutex_lock(&pcdev->lock); + ret = _pse_pi_get_voltage(rdev); + if (!ret) { + dev_err(pcdev->dev, "Voltage null\n"); + ret = -ERANGE; + goto out; + } + if (ret < 0) + goto out; + + tmp_64 = ret; + tmp_64 *= max_uA; + /* mW = uA * uV / 1000000000 */ + mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); + ret = ops->pi_set_pw_limit(pcdev, id, mW); +out: + mutex_unlock(&pcdev->lock); + + return ret; +} + +static const struct regulator_ops pse_pi_ops = { + .is_enabled = pse_pi_is_enabled, + .enable = pse_pi_enable, + .disable = pse_pi_disable, + .get_voltage = pse_pi_get_voltage, + .get_current_limit = pse_pi_get_current_limit, + .set_current_limit = pse_pi_set_current_limit, +}; + +static int +devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev, + char *name, int id) +{ + struct regulator_init_data *rinit_data; + struct regulator_config rconfig = {0}; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + + rinit_data = devm_kzalloc(pcdev->dev, sizeof(*rinit_data), + GFP_KERNEL); + if (!rinit_data) + return -ENOMEM; + + rdesc = devm_kzalloc(pcdev->dev, sizeof(*rdesc), GFP_KERNEL); + if (!rdesc) + return -ENOMEM; + + /* Regulator descriptor id have to be the same as its associated + * PSE PI id for the well functioning of the PSE controls. + */ + rdesc->id = id; + rdesc->name = name; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->ops = &pse_pi_ops; + rdesc->owner = pcdev->owner; + + rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; + + if (pcdev->ops->pi_set_pw_limit) + rinit_data->constraints.valid_ops_mask |= + REGULATOR_CHANGE_CURRENT; + + rinit_data->supply_regulator = "vpwr"; + + rconfig.dev = pcdev->dev; + rconfig.driver_data = pcdev; + rconfig.init_data = rinit_data; + rconfig.of_node = pcdev->pi[id].np; + + rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig); + if (IS_ERR(rdev)) { + dev_err_probe(pcdev->dev, PTR_ERR(rdev), + "Failed to register regulator\n"); + return PTR_ERR(rdev); + } + + pcdev->pi[id].rdev = rdev; + + return 0; +} + +static void __pse_pw_d_release(struct kref *kref) +{ + struct pse_power_domain *pw_d = container_of(kref, + struct pse_power_domain, + refcnt); + + regulator_put(pw_d->supply); + xa_erase(&pse_pw_d_map, pw_d->id); + mutex_unlock(&pse_pw_d_mutex); +} + +/** + * pse_flush_pw_ds - flush all PSE power domains of a PSE + * @pcdev: a pointer to the initialized PSE controller device + */ +static void pse_flush_pw_ds(struct pse_controller_dev *pcdev) +{ + struct pse_power_domain *pw_d; + int i; + + for (i = 0; i < pcdev->nr_lines; i++) { + if (!pcdev->pi[i].pw_d) + continue; + + pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id); + if (!pw_d) + continue; + + kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release, + &pse_pw_d_mutex); + } +} + +/** + * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device + * @dev: device that is registering this PSE power domain + * + * Return: Pointer to the newly allocated PSE power domain or error pointers + */ +static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev) +{ + struct pse_power_domain *pw_d; + int index, ret; + + pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL); + if (!pw_d) + return ERR_PTR(-ENOMEM); + + ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT), + GFP_KERNEL); + if (ret) + return ERR_PTR(ret); + + kref_init(&pw_d->refcnt); + pw_d->id = index; + return pw_d; +} + +/** + * pse_register_pw_ds - register the PSE power domains for a PSE + * @pcdev: a pointer to the PSE controller device + * + * Return: 0 on success and failure value on error + */ +static int pse_register_pw_ds(struct pse_controller_dev *pcdev) +{ + int i, ret = 0; + + mutex_lock(&pse_pw_d_mutex); + for (i = 0; i < pcdev->nr_lines; i++) { + struct regulator_dev *rdev = pcdev->pi[i].rdev; + struct pse_power_domain *pw_d; + struct regulator *supply; + bool present = false; + unsigned long index; + + /* No regulator or regulator parent supply registered. + * We need a regulator parent to register a PSE power domain + */ + if (!rdev || !rdev->supply) + continue; + + xa_for_each(&pse_pw_d_map, index, pw_d) { + /* Power supply already registered as a PSE power + * domain. + */ + if (regulator_is_equal(pw_d->supply, rdev->supply)) { + present = true; + pcdev->pi[i].pw_d = pw_d; + break; + } + } + if (present) { + kref_get(&pw_d->refcnt); + continue; + } + + pw_d = devm_pse_alloc_pw_d(pcdev->dev); + if (IS_ERR(pw_d)) { + ret = PTR_ERR(pw_d); + goto out; + } + + supply = regulator_get(&rdev->dev, rdev->supply_name); + if (IS_ERR(supply)) { + xa_erase(&pse_pw_d_map, pw_d->id); + ret = PTR_ERR(supply); + goto out; + } + + pw_d->supply = supply; + if (pcdev->supp_budget_eval_strategies) + pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies; + else + pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED; + kref_init(&pw_d->refcnt); + pcdev->pi[i].pw_d = pw_d; + } + +out: + mutex_unlock(&pse_pw_d_mutex); + return ret; +} + +/** + * pse_send_ntf_worker - Worker to send PSE notifications + * @work: work object + * + * Manage and send PSE netlink notifications using a workqueue to avoid + * deadlock between pcdev_lock and pse_list_mutex. + */ +static void pse_send_ntf_worker(struct work_struct *work) +{ + struct pse_controller_dev *pcdev; + struct pse_ntf ntf; + + pcdev = container_of(work, struct pse_controller_dev, ntf_work); + + while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) { + struct net_device *netdev; + struct pse_control *psec; + + psec = pse_control_find_by_id(pcdev, ntf.id); + rtnl_lock(); + netdev = pse_control_get_netdev(psec); + if (netdev) + ethnl_pse_send_ntf(netdev, ntf.notifs); + rtnl_unlock(); + pse_control_put(psec); + } +} + +/** + * pse_controller_register - register a PSE controller device + * @pcdev: a pointer to the initialized PSE controller device + * + * Return: 0 on success and failure value on error + */ +int pse_controller_register(struct pse_controller_dev *pcdev) +{ + size_t reg_name_len; + int ret, i; + + mutex_init(&pcdev->lock); + INIT_LIST_HEAD(&pcdev->pse_control_head); + spin_lock_init(&pcdev->ntf_fifo_lock); + ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL); + if (ret) { + dev_err(pcdev->dev, "failed to allocate kfifo notifications\n"); + return ret; + } + INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker); + + if (!pcdev->nr_lines) + pcdev->nr_lines = 1; + + if (!pcdev->ops->pi_get_admin_state || + !pcdev->ops->pi_get_pw_status) { + dev_err(pcdev->dev, + "Mandatory status report callbacks are missing"); + return -EINVAL; + } + + ret = of_load_pse_pis(pcdev); + if (ret) + return ret; + + if (pcdev->ops->setup_pi_matrix) { + ret = pcdev->ops->setup_pi_matrix(pcdev); + if (ret) + return ret; + } + + /* Each regulator name len is pcdev dev name + 7 char + + * int max digit number (10) + 1 + */ + reg_name_len = strlen(dev_name(pcdev->dev)) + 18; + + /* Register PI regulators */ + for (i = 0; i < pcdev->nr_lines; i++) { + char *reg_name; + + /* Do not register regulator for PIs not described */ + if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np) + continue; + + reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL); + if (!reg_name) + return -ENOMEM; + + snprintf(reg_name, reg_name_len, "pse-%s_pi%d", + dev_name(pcdev->dev), i); + + ret = devm_pse_pi_regulator_register(pcdev, reg_name, i); + if (ret) + return ret; + } + + ret = pse_register_pw_ds(pcdev); + if (ret) + return ret; + + mutex_lock(&pse_list_mutex); + list_add(&pcdev->list, &pse_controller_list); + mutex_unlock(&pse_list_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(pse_controller_register); + +/** + * pse_controller_unregister - unregister a PSE controller device + * @pcdev: a pointer to the PSE controller device + */ +void pse_controller_unregister(struct pse_controller_dev *pcdev) +{ + pse_flush_pw_ds(pcdev); + pse_release_pis(pcdev); + if (pcdev->irq) + disable_irq(pcdev->irq); + cancel_work_sync(&pcdev->ntf_work); + kfifo_free(&pcdev->ntf_fifo); + mutex_lock(&pse_list_mutex); + list_del(&pcdev->list); + mutex_unlock(&pse_list_mutex); +} +EXPORT_SYMBOL_GPL(pse_controller_unregister); + +static void devm_pse_controller_release(struct device *dev, void *res) +{ + pse_controller_unregister(*(struct pse_controller_dev **)res); +} + +/** + * devm_pse_controller_register - resource managed pse_controller_register() + * @dev: device that is registering this PSE controller + * @pcdev: a pointer to the initialized PSE controller device + * + * Managed pse_controller_register(). For PSE controllers registered by + * this function, pse_controller_unregister() is automatically called on + * driver detach. See pse_controller_register() for more information. + * + * Return: 0 on success and failure value on error + */ +int devm_pse_controller_register(struct device *dev, + struct pse_controller_dev *pcdev) +{ + struct pse_controller_dev **pcdevp; + int ret; + + pcdevp = devres_alloc(devm_pse_controller_release, sizeof(*pcdevp), + GFP_KERNEL); + if (!pcdevp) + return -ENOMEM; + + ret = pse_controller_register(pcdev); + if (ret) { + devres_free(pcdevp); + return ret; + } + + *pcdevp = pcdev; + devres_add(dev, pcdevp); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_pse_controller_register); + +struct pse_irq { + struct pse_controller_dev *pcdev; + struct pse_irq_desc desc; + unsigned long *notifs; + unsigned long *notifs_mask; +}; + +/** + * pse_to_regulator_notifs - Convert PSE notifications to Regulator + * notifications + * @notifs: PSE notifications + * + * Return: Regulator notifications + */ +static unsigned long pse_to_regulator_notifs(unsigned long notifs) +{ + unsigned long rnotifs = 0; + + if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT) + rnotifs |= REGULATOR_EVENT_OVER_CURRENT; + if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP) + rnotifs |= REGULATOR_EVENT_OVER_TEMP; + + return rnotifs; +} + +/** + * pse_set_config_isr - Set PSE control config according to the PSE + * notifications + * @pcdev: a pointer to the PSE + * @id: index of the PSE control + * @notifs: PSE event notifications + * + * Return: 0 on success and failure value on error + */ +static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id, + unsigned long notifs) +{ + int ret = 0; + + if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC) + return 0; + + if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) && + ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) || + (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) { + dev_dbg(pcdev->dev, + "PI %d: error, connection and disconnection reported simultaneously", + id); + return -EINVAL; + } + + if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) { + struct netlink_ext_ack extack; + + pcdev->pi[id].isr_pd_detected = true; + if (pcdev->pi[id].admin_state_enabled) { + ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, + &extack); + if (ret == -ERANGE) + ret = 0; + } + } else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) { + if (pcdev->pi[id].admin_state_enabled && + pcdev->pi[id].isr_pd_detected) + ret = _pse_pi_disable(pcdev, id); + pcdev->pi[id].isr_pd_detected = false; + } + + return ret; +} + +/** + * pse_isr - IRQ handler for PSE + * @irq: irq number + * @data: pointer to user interrupt structure + * + * Return: irqreturn_t - status of IRQ + */ +static irqreturn_t pse_isr(int irq, void *data) +{ + struct pse_controller_dev *pcdev; + struct pse_irq_desc *desc; + struct pse_irq *h = data; + int ret, i; + + desc = &h->desc; + pcdev = h->pcdev; + + /* Clear notifs mask */ + memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs)); + bitmap_zero(h->notifs_mask, pcdev->nr_lines); + mutex_lock(&pcdev->lock); + ret = desc->map_event(irq, pcdev, h->notifs, h->notifs_mask); + if (ret || bitmap_empty(h->notifs_mask, pcdev->nr_lines)) { + mutex_unlock(&pcdev->lock); + return IRQ_NONE; + } + + for_each_set_bit(i, h->notifs_mask, pcdev->nr_lines) { + unsigned long notifs, rnotifs; + struct pse_ntf ntf = {}; + + /* Do nothing PI not described */ + if (!pcdev->pi[i].rdev) + continue; + + notifs = h->notifs[i]; + if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) { + ret = pse_set_config_isr(pcdev, i, notifs); + if (ret) + notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR; + } + + dev_dbg(h->pcdev->dev, + "Sending PSE notification EVT 0x%lx\n", notifs); + + ntf.notifs = notifs; + ntf.id = i; + kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, + &pcdev->ntf_fifo_lock); + schedule_work(&pcdev->ntf_work); + + rnotifs = pse_to_regulator_notifs(notifs); + regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs, + NULL); + } + + mutex_unlock(&pcdev->lock); + + return IRQ_HANDLED; +} + +/** + * devm_pse_irq_helper - Register IRQ based PSE event notifier + * @pcdev: a pointer to the PSE + * @irq: the irq value to be passed to request_irq + * @irq_flags: the flags to be passed to request_irq + * @d: PSE interrupt description + * + * Return: 0 on success and errno on failure + */ +int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, + int irq_flags, const struct pse_irq_desc *d) +{ + struct device *dev = pcdev->dev; + size_t irq_name_len; + struct pse_irq *h; + char *irq_name; + int ret; + + if (!d || !d->map_event || !d->name) + return -EINVAL; + + h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->pcdev = pcdev; + h->desc = *d; + + /* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */ + irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1; + irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL); + if (!irq_name) + return -ENOMEM; + + snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev), + d->name); + + h->notifs = devm_kcalloc(dev, pcdev->nr_lines, + sizeof(*h->notifs), GFP_KERNEL); + if (!h->notifs) + return -ENOMEM; + + h->notifs_mask = devm_bitmap_zalloc(dev, pcdev->nr_lines, GFP_KERNEL); + if (!h->notifs_mask) + return -ENOMEM; + + ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr, + IRQF_ONESHOT | irq_flags, + irq_name, h); + if (ret) + dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq); + + pcdev->irq = irq; + return ret; +} +EXPORT_SYMBOL_GPL(devm_pse_irq_helper); + +/* PSE control section */ + +static void __pse_control_release(struct kref *kref) +{ + struct pse_control *psec = container_of(kref, struct pse_control, + refcnt); + + lockdep_assert_held(&pse_list_mutex); + + if (psec->pcdev->pi[psec->id].admin_state_enabled) + regulator_disable(psec->ps); + regulator_put(psec->ps); + + module_put(psec->pcdev->owner); + + list_del(&psec->list); + kfree(psec); +} + +static void __pse_control_put_internal(struct pse_control *psec) +{ + lockdep_assert_held(&pse_list_mutex); + + kref_put(&psec->refcnt, __pse_control_release); +} + +/** + * pse_control_put - free the PSE control + * @psec: PSE control pointer + */ +void pse_control_put(struct pse_control *psec) +{ + if (IS_ERR_OR_NULL(psec)) + return; + + mutex_lock(&pse_list_mutex); + __pse_control_put_internal(psec); + mutex_unlock(&pse_list_mutex); +} +EXPORT_SYMBOL_GPL(pse_control_put); + +static struct pse_control * +pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, + struct phy_device *phydev) +{ + struct pse_control *psec; + int ret; + + lockdep_assert_held(&pse_list_mutex); + + list_for_each_entry(psec, &pcdev->pse_control_head, list) { + if (psec->id == index) { + kref_get(&psec->refcnt); + return psec; + } + } + + psec = kzalloc_obj(*psec); + if (!psec) + return ERR_PTR(-ENOMEM); + + if (!try_module_get(pcdev->owner)) { + ret = -ENODEV; + goto free_psec; + } + + if (!pcdev->ops->pi_get_admin_state) { + ret = -EOPNOTSUPP; + goto free_psec; + } + + /* Initialize admin_state_enabled before the regulator_get. This + * aims to have the right value reported in the first is_enabled + * call in case of control managed by software. + */ + ret = pse_pi_is_hw_enabled(pcdev, index); + if (ret < 0) + goto free_psec; + + pcdev->pi[index].admin_state_enabled = ret; + psec->ps = regulator_get_exclusive(pcdev->dev, + rdev_get_name(pcdev->pi[index].rdev)); + if (IS_ERR(psec->ps)) { + ret = PTR_ERR(psec->ps); + goto put_module; + } + + psec->pcdev = pcdev; + list_add(&psec->list, &pcdev->pse_control_head); + psec->id = index; + psec->attached_phydev = phydev; + kref_init(&psec->refcnt); + + return psec; + +put_module: + module_put(pcdev->owner); +free_psec: + kfree(psec); + + return ERR_PTR(ret); +} + +/** + * of_pse_match_pi - Find the PSE PI id matching the device node phandle + * @pcdev: a pointer to the PSE controller device + * @np: a pointer to the device node + * + * Return: id of the PSE PI, -EINVAL if not found + */ +static int of_pse_match_pi(struct pse_controller_dev *pcdev, + struct device_node *np) +{ + int i; + + for (i = 0; i < pcdev->nr_lines; i++) { + if (pcdev->pi[i].np == np) + return i; + } + + return -EINVAL; +} + +/** + * psec_id_xlate - translate pse_spec to the PSE line number according + * to the number of pse-cells in case of no pse_pi node + * @pcdev: a pointer to the PSE controller device + * @pse_spec: PSE line specifier as found in the device tree + * + * Return: 0 if #pse-cells = <0>. Return PSE line number otherwise. + */ +static int psec_id_xlate(struct pse_controller_dev *pcdev, + const struct of_phandle_args *pse_spec) +{ + if (!pcdev->of_pse_n_cells) + return 0; + + if (pcdev->of_pse_n_cells > 1 || + pse_spec->args[0] >= pcdev->nr_lines) + return -EINVAL; + + return pse_spec->args[0]; +} + +struct pse_control *of_pse_control_get(struct device_node *node, + struct phy_device *phydev) +{ + struct pse_controller_dev *r, *pcdev; + struct of_phandle_args args; + struct pse_control *psec; + int psec_id; + int ret; + + if (!node) + return ERR_PTR(-EINVAL); + + ret = of_parse_phandle_with_args(node, "pses", "#pse-cells", 0, &args); + if (ret) + return ERR_PTR(ret); + + mutex_lock(&pse_list_mutex); + pcdev = NULL; + list_for_each_entry(r, &pse_controller_list, list) { + if (!r->no_of_pse_pi) { + ret = of_pse_match_pi(r, args.np); + if (ret >= 0) { + pcdev = r; + psec_id = ret; + break; + } + } else if (args.np == r->dev->of_node) { + pcdev = r; + break; + } + } + + if (!pcdev) { + psec = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + if (WARN_ON(args.args_count != pcdev->of_pse_n_cells)) { + psec = ERR_PTR(-EINVAL); + goto out; + } + + if (pcdev->no_of_pse_pi) { + psec_id = psec_id_xlate(pcdev, &args); + if (psec_id < 0) { + psec = ERR_PTR(psec_id); + goto out; + } + } + + /* pse_list_mutex also protects the pcdev's pse_control list */ + psec = pse_control_get_internal(pcdev, psec_id, phydev); + +out: + mutex_unlock(&pse_list_mutex); + of_node_put(args.np); + + return psec; +} +EXPORT_SYMBOL_GPL(of_pse_control_get); + +/** + * pse_get_sw_admin_state - Convert the software admin state to c33 or podl + * admin state value used in the standard + * @psec: PSE control pointer + * @admin_state: a pointer to the admin_state structure + */ +static void pse_get_sw_admin_state(struct pse_control *psec, + struct pse_admin_state *admin_state) +{ + struct pse_pi *pi = &psec->pcdev->pi[psec->id]; + + if (pse_has_podl(psec)) { + if (pi->admin_state_enabled) + admin_state->podl_admin_state = + ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; + else + admin_state->podl_admin_state = + ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; + } + if (pse_has_c33(psec)) { + if (pi->admin_state_enabled) + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + } +} + +/** + * pse_ethtool_get_status - get status of PSE control + * @psec: PSE control pointer + * @extack: extack for reporting useful error messages + * @status: struct to store PSE status + * + * Return: 0 on success and failure value on error + */ +int pse_ethtool_get_status(struct pse_control *psec, + struct netlink_ext_ack *extack, + struct ethtool_pse_control_status *status) +{ + struct pse_admin_state admin_state = {0}; + struct pse_pw_status pw_status = {0}; + const struct pse_controller_ops *ops; + struct pse_controller_dev *pcdev; + struct pse_pi *pi; + int ret; + + pcdev = psec->pcdev; + ops = pcdev->ops; + + pi = &pcdev->pi[psec->id]; + mutex_lock(&pcdev->lock); + if (pi->pw_d) { + status->pw_d_id = pi->pw_d->id; + if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) { + pse_get_sw_admin_state(psec, &admin_state); + } else { + ret = ops->pi_get_admin_state(pcdev, psec->id, + &admin_state); + if (ret) + goto out; + } + status->podl_admin_state = admin_state.podl_admin_state; + status->c33_admin_state = admin_state.c33_admin_state; + + switch (pi->pw_d->budget_eval_strategy) { + case PSE_BUDGET_EVAL_STRAT_STATIC: + status->prio_max = pcdev->nr_lines - 1; + status->prio = pi->prio; + break; + case PSE_BUDGET_EVAL_STRAT_DYNAMIC: + status->prio_max = pcdev->pis_prio_max; + if (ops->pi_get_prio) { + ret = ops->pi_get_prio(pcdev, psec->id); + if (ret < 0) + goto out; + + status->prio = ret; + } + break; + default: + break; + } + } + + ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status); + if (ret) + goto out; + status->podl_pw_status = pw_status.podl_pw_status; + status->c33_pw_status = pw_status.c33_pw_status; + + if (ops->pi_get_ext_state) { + struct pse_ext_state_info ext_state_info = {0}; + + ret = ops->pi_get_ext_state(pcdev, psec->id, + &ext_state_info); + if (ret) + goto out; + + memcpy(&status->c33_ext_state_info, + &ext_state_info.c33_ext_state_info, + sizeof(status->c33_ext_state_info)); + } + + if (ops->pi_get_pw_class) { + ret = ops->pi_get_pw_class(pcdev, psec->id); + if (ret < 0) + goto out; + + status->c33_pw_class = ret; + } + + if (ops->pi_get_actual_pw) { + ret = ops->pi_get_actual_pw(pcdev, psec->id); + if (ret < 0) + goto out; + + status->c33_actual_pw = ret; + } + + if (ops->pi_get_pw_limit) { + ret = ops->pi_get_pw_limit(pcdev, psec->id); + if (ret < 0) + goto out; + + status->c33_avail_pw_limit = ret; + } + + if (ops->pi_get_pw_limit_ranges) { + struct pse_pw_limit_ranges pw_limit_ranges = {0}; + + ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id, + &pw_limit_ranges); + if (ret < 0) + goto out; + + status->c33_pw_limit_ranges = + pw_limit_ranges.c33_pw_limit_ranges; + status->c33_pw_limit_nb_ranges = ret; + } +out: + mutex_unlock(&psec->pcdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(pse_ethtool_get_status); + +static int pse_ethtool_c33_set_config(struct pse_control *psec, + const struct pse_control_config *config) +{ + int err = 0; + + /* Look at admin_state_enabled status to not call regulator_enable + * or regulator_disable twice creating a regulator counter mismatch + */ + switch (config->c33_admin_control) { + case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED: + /* We could have mismatch between admin_state_enabled and + * state reported by regulator_is_enabled. This can occur when + * the PI is forcibly turn off by the controller. Call + * regulator_disable on that case to fix the counters state. + */ + if (psec->pcdev->pi[psec->id].admin_state_enabled && + !regulator_is_enabled(psec->ps)) { + err = regulator_disable(psec->ps); + if (err) + break; + } + if (!psec->pcdev->pi[psec->id].admin_state_enabled) + err = regulator_enable(psec->ps); + break; + case ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED: + if (psec->pcdev->pi[psec->id].admin_state_enabled) + err = regulator_disable(psec->ps); + break; + default: + err = -EOPNOTSUPP; + } + + return err; +} + +static int pse_ethtool_podl_set_config(struct pse_control *psec, + const struct pse_control_config *config) +{ + int err = 0; + + /* Look at admin_state_enabled status to not call regulator_enable + * or regulator_disable twice creating a regulator counter mismatch + */ + switch (config->podl_admin_control) { + case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED: + if (!psec->pcdev->pi[psec->id].admin_state_enabled) + err = regulator_enable(psec->ps); + break; + case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED: + if (psec->pcdev->pi[psec->id].admin_state_enabled) + err = regulator_disable(psec->ps); + break; + default: + err = -EOPNOTSUPP; + } + + return err; +} + +/** + * pse_ethtool_set_config - set PSE control configuration + * @psec: PSE control pointer + * @extack: extack for reporting useful error messages + * @config: Configuration of the test to run + * + * Return: 0 on success and failure value on error + */ +int pse_ethtool_set_config(struct pse_control *psec, + struct netlink_ext_ack *extack, + const struct pse_control_config *config) +{ + int err = 0; + + if (pse_has_c33(psec) && config->c33_admin_control) { + err = pse_ethtool_c33_set_config(psec, config); + if (err) + return err; + } + + if (pse_has_podl(psec) && config->podl_admin_control) + err = pse_ethtool_podl_set_config(psec, config); + + return err; +} +EXPORT_SYMBOL_GPL(pse_ethtool_set_config); + +/** + * pse_pi_update_pw_budget - Update PSE power budget allocated with new + * power in mW + * @pcdev: a pointer to the PSE controller device + * @id: index of the PSE PI + * @pw_req: power requested + * @extack: extack for reporting useful error messages + * + * Return: Previous power allocated on success and failure value on error + */ +static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id, + const unsigned int pw_req, + struct netlink_ext_ack *extack) +{ + struct pse_pi *pi = &pcdev->pi[id]; + int previous_pw_allocated; + int pw_diff, ret = 0; + + /* We don't want pw_allocated_mW value change in the middle of an + * power budget update + */ + mutex_lock(&pcdev->lock); + previous_pw_allocated = pi->pw_allocated_mW; + pw_diff = pw_req - previous_pw_allocated; + if (!pw_diff) { + goto out; + } else if (pw_diff > 0) { + ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff); + if (ret) { + NL_SET_ERR_MSG_FMT(extack, + "PI %d: not enough power budget available", + id); + goto out; + } + + } else { + regulator_free_power_budget(pi->pw_d->supply, -pw_diff); + } + pi->pw_allocated_mW = pw_req; + ret = previous_pw_allocated; + +out: + mutex_unlock(&pcdev->lock); + return ret; +} + +/** + * pse_ethtool_set_pw_limit - set PSE control power limit + * @psec: PSE control pointer + * @extack: extack for reporting useful error messages + * @pw_limit: power limit value in mW + * + * Return: 0 on success and failure value on error + */ +int pse_ethtool_set_pw_limit(struct pse_control *psec, + struct netlink_ext_ack *extack, + const unsigned int pw_limit) +{ + int uV, uA, ret, previous_pw_allocated = 0; + s64 tmp_64; + + if (pw_limit > MAX_PI_PW) + return -ERANGE; + + ret = regulator_get_voltage(psec->ps); + if (!ret) { + NL_SET_ERR_MSG(extack, + "Can't calculate the current, PSE voltage read is 0"); + return -ERANGE; + } + if (ret < 0) { + NL_SET_ERR_MSG(extack, + "Error reading PSE voltage"); + return ret; + } + uV = ret; + + tmp_64 = pw_limit; + tmp_64 *= 1000000000ull; + /* uA = mW * 1000000000 / uV */ + uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); + + /* Update power budget only in software power control case and + * if a Power Device is powered. + */ + if (pse_pw_d_is_sw_pw_control(psec->pcdev, + psec->pcdev->pi[psec->id].pw_d) && + psec->pcdev->pi[psec->id].admin_state_enabled && + psec->pcdev->pi[psec->id].isr_pd_detected) { + ret = pse_pi_update_pw_budget(psec->pcdev, psec->id, + pw_limit, extack); + if (ret < 0) + return ret; + previous_pw_allocated = ret; + } + + ret = regulator_set_current_limit(psec->ps, 0, uA); + if (ret < 0 && previous_pw_allocated) { + pse_pi_update_pw_budget(psec->pcdev, psec->id, + previous_pw_allocated, extack); + } + + return ret; +} +EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit); + +/** + * pse_ethtool_set_prio - Set PSE PI priority according to the budget + * evaluation strategy + * @psec: PSE control pointer + * @extack: extack for reporting useful error messages + * @prio: priovity value + * + * Return: 0 on success and failure value on error + */ +int pse_ethtool_set_prio(struct pse_control *psec, + struct netlink_ext_ack *extack, + unsigned int prio) +{ + struct pse_controller_dev *pcdev = psec->pcdev; + const struct pse_controller_ops *ops; + int ret = 0; + + if (!pcdev->pi[psec->id].pw_d) { + NL_SET_ERR_MSG(extack, "no power domain attached"); + return -EOPNOTSUPP; + } + + /* We don't want priority change in the middle of an + * enable/disable call or a priority mode change + */ + mutex_lock(&pcdev->lock); + switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) { + case PSE_BUDGET_EVAL_STRAT_STATIC: + if (prio >= pcdev->nr_lines) { + NL_SET_ERR_MSG_FMT(extack, + "priority %d exceed priority max %d", + prio, pcdev->nr_lines); + ret = -ERANGE; + goto out; + } + + pcdev->pi[psec->id].prio = prio; + pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d); + break; + + case PSE_BUDGET_EVAL_STRAT_DYNAMIC: + ops = psec->pcdev->ops; + if (!ops->pi_set_prio) { + NL_SET_ERR_MSG(extack, + "pse driver does not support setting port priority"); + ret = -EOPNOTSUPP; + goto out; + } + + if (prio > pcdev->pis_prio_max) { + NL_SET_ERR_MSG_FMT(extack, + "priority %d exceed priority max %d", + prio, pcdev->pis_prio_max); + ret = -ERANGE; + goto out; + } + + ret = ops->pi_set_prio(pcdev, psec->id, prio); + break; + + default: + ret = -EOPNOTSUPP; + } + +out: + mutex_unlock(&pcdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(pse_ethtool_set_prio); + +bool pse_has_podl(struct pse_control *psec) +{ + return psec->pcdev->types & ETHTOOL_PSE_PODL; +} +EXPORT_SYMBOL_GPL(pse_has_podl); + +bool pse_has_c33(struct pse_control *psec) +{ + return psec->pcdev->types & ETHTOOL_PSE_C33; +} +EXPORT_SYMBOL_GPL(pse_has_c33); diff --git a/drivers/net/pse-pd/pse_regulator.c b/drivers/net/pse-pd/pse_regulator.c new file mode 100644 index 000000000..6ce6773ff --- /dev/null +++ b/drivers/net/pse-pd/pse_regulator.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Driver for the regulator based Ethernet Power Sourcing Equipment, without +// auto classification support. +// +// Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> +// + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pse-pd/pse.h> +#include <linux/regulator/consumer.h> + +struct pse_reg_priv { + struct pse_controller_dev pcdev; + struct regulator *ps; /*power source */ + enum ethtool_podl_pse_admin_state admin_state; +}; + +static struct pse_reg_priv *to_pse_reg(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct pse_reg_priv, pcdev); +} + +static int +pse_reg_pi_enable(struct pse_controller_dev *pcdev, int id) +{ + struct pse_reg_priv *priv = to_pse_reg(pcdev); + int ret; + + ret = regulator_enable(priv->ps); + if (ret) + return ret; + + priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; + return 0; +} + +static int +pse_reg_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + struct pse_reg_priv *priv = to_pse_reg(pcdev); + int ret; + + ret = regulator_disable(priv->ps); + if (ret) + return ret; + + priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; + return 0; +} + +static int +pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct pse_reg_priv *priv = to_pse_reg(pcdev); + + admin_state->podl_admin_state = priv->admin_state; + + return 0; +} + +static int +pse_reg_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct pse_reg_priv *priv = to_pse_reg(pcdev); + int ret; + + ret = regulator_is_enabled(priv->ps); + if (ret < 0) + return ret; + + if (!ret) + pw_status->podl_pw_status = + ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED; + else + pw_status->podl_pw_status = + ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING; + + return 0; +} + +static const struct pse_controller_ops pse_reg_ops = { + .pi_get_admin_state = pse_reg_pi_get_admin_state, + .pi_get_pw_status = pse_reg_pi_get_pw_status, + .pi_enable = pse_reg_pi_enable, + .pi_disable = pse_reg_pi_disable, +}; + +static int +pse_reg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pse_reg_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!pdev->dev.of_node) + return -ENOENT; + + priv->ps = devm_regulator_get_exclusive(dev, "pse"); + if (IS_ERR(priv->ps)) + return dev_err_probe(dev, PTR_ERR(priv->ps), + "failed to get PSE regulator.\n"); + + platform_set_drvdata(pdev, priv); + + ret = regulator_is_enabled(priv->ps); + if (ret < 0) + return ret; + + if (ret) + priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; + else + priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; + + priv->pcdev.owner = THIS_MODULE; + priv->pcdev.ops = &pse_reg_ops; + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_PODL; + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) { + dev_err(dev, "failed to register PSE controller (%pe)\n", + ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static const __maybe_unused struct of_device_id pse_reg_of_match[] = { + { .compatible = "podl-pse-regulator", }, + { }, +}; +MODULE_DEVICE_TABLE(of, pse_reg_of_match); + +static struct platform_driver pse_reg_driver = { + .probe = pse_reg_probe, + .driver = { + .name = "PSE regulator", + .of_match_table = of_match_ptr(pse_reg_of_match), + }, +}; +module_platform_driver(pse_reg_driver); + +MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("regulator based Ethernet Power Sourcing Equipment"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pse-regulator"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu-core.c b/drivers/net/pse-pd/realtek-pse-mcu-core.c new file mode 100644 index 000000000..e64f13ea6 --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-core.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the microcontroller (MCU) fronting PSE silicon on various + * Realtek-based managed switches. The MCU speaks a 12-byte fixed-frame + * management protocol; this driver covers two generations of the + * protocol via a per-dialect opcode table and response parsers. + * + * Many PoE switch designs put a dedicated microcontroller in front of the + * actual PSE silicon: the host CPU talks to the MCU over I2C/SMBus or + * UART, and the MCU in turn manages the PSE chips on the board. The MCU + * speaks a small message-based protocol. The PSE chips themselves are not + * accessed directly; everything goes through MCU commands. + * + * This driver targets that architecture for the Realtek-family protocol. + * Two generations are supported: Gen1 being used on older switches where + * the MCU fronts and manages Broadcom PSE silicon; Gen2 being used with + * Realtek PSE silicon. The two share frame format and a sum-mod-256 + * checksum but diverge on opcode numbers and on a few response layouts; + * this is handled by the per-dialect opcode table and parser hooks. + * + * Out of scope: PSE chips that are interfaced directly from the host + * without a management MCU, MCU designs that speak an unrelated protocol + * family, and "dumb PSE" modes where no host control is wired up at all. + * + * This core module implements the protocol, decoding/encoding of MCU + * responses, and the pse_controller_ops integration. Transport modules + * (realtek-pse-mcu-i2c, realtek-pse-mcu-uart) provide the send/recv + * callbacks. + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/pse-pd/pse.h> +#include <linux/unaligned.h> + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_DEVICE_ID_RTL8238B 0x0138 +#define RTPSE_MCU_DEVICE_ID_RTL8239 0x0039 +#define RTPSE_MCU_DEVICE_ID_RTL8239C 0x0139 +#define RTPSE_MCU_DEVICE_ID_BCM59111 0xe111 +#define RTPSE_MCU_DEVICE_ID_BCM59121 0xe121 + +#define RTPSE_MCU_PORT_STS_DISABLED 0x00 +#define RTPSE_MCU_PORT_STS_SEARCHING 0x01 +#define RTPSE_MCU_PORT_STS_DELIVERING 0x02 +#define RTPSE_MCU_PORT_STS_TEST 0x03 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_FAULT 0x04 +#define RTPSE_MCU_PORT_STS_OTHER_FAULT 0x05 /* Gen1-only; reserved on Gen2 */ +#define RTPSE_MCU_PORT_STS_REQUESTING 0x06 + +/* RTPSE_MCU_PORT_SET_POWER_LIMIT_TYPE values */ +#define RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER 0x02 + +#define RTPSE_MCU_MAX_PORTS 48 +#define RTPSE_MCU_PORT_MAX_PRIORITY 3 + +/* Bounded resends when the MCU replies NOT_READY (busy). */ +#define RTPSE_MCU_NOT_READY_RETRIES 3 + +/* Nominal PSE rail; 802.3at/bt operating range. */ +#define RTPSE_MCU_PSE_VOLTAGE_UV 54000000 + +enum rtpse_mcu_cmd { + RTPSE_MCU_CMD_SET_GLOBAL_STATE, + RTPSE_MCU_CMD_GET_SYSTEM_INFO, + RTPSE_MCU_CMD_GET_EXT_CONFIG, + + RTPSE_MCU_CMD_PORT_ENABLE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + RTPSE_MCU_CMD_PORT_SET_PRIORITY, + RTPSE_MCU_CMD_PORT_GET_STATUS, + RTPSE_MCU_CMD_PORT_GET_POWER_STATS, + RTPSE_MCU_CMD_PORT_GET_CONFIG, + RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG, + + RTPSE_MCU_NUM_CMDS, +}; + +struct rtpse_mcu_opcode { + u8 op; + bool valid; +}; + +/* Shorthand for the designated-initializer entries in dialect opcode tables. */ +#define RTPSE_MCU_OP(opc) { .op = (opc), .valid = true } + +/* Parsed MCU response structures (decoded from rtpse_mcu_msg replies) */ + +struct rtpse_mcu_info { + u8 max_ports; + bool system_enable; + u16 device_id; + u8 mcu_type; +}; + +struct rtpse_mcu_ext_config { + u8 num_of_pses; +}; + +struct rtpse_mcu_port_status { + u8 sts1; + u8 sts2; + u8 sts3; +}; + +struct rtpse_mcu_port_measurement { + u16 voltage_raw; /* 64.45mV/LSB */ + u16 current_raw; /* 1mA/LSB */ + u16 temperature_raw; /* T(mC) = 1250 * (220 - raw) */ + u16 power_raw; /* 100mW/LSB */ +}; + +struct rtpse_mcu_port_config { + bool enable; +}; + +struct rtpse_mcu_port_ext_config { + u8 max_power; + u8 priority; +}; + +struct rtpse_mcu_dialect { + struct rtpse_mcu_opcode opcode[RTPSE_MCU_NUM_CMDS]; + + /* + * Response parsers for the fields that differ between dialects; each + * dialect supplies its own. Other responses share one layout and are + * decoded directly - a dialect that diverges there must add a hook, + * as a mismatched layout cannot be detected (the checksum still passes). + */ + void (*parse_system_info)(const u8 *payload, struct rtpse_mcu_info *info); + int (*parse_port_class)(const struct rtpse_mcu_port_status *status); + const char *(*mcu_type_str)(unsigned int mcu_type); +}; + +struct rtpse_mcu_chip_info { + const char *name; + u32 max_mW_per_port; + enum rtpse_mcu_cmd pw_set_cmd; /* command used by set_pw_limit */ + u32 pw_set_lsb_mW; /* LSB of pw_set_cmd value, in mW */ + u32 pw_read_lsb_mW; /* LSB of ext_config.max_power read-back, in mW */ +}; + +static const struct rtpse_mcu_chip_info rtl8238b_info = { + .max_mW_per_port = 30000, + .name = "RTL8238B", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info rtl8239_info = { + .max_mW_per_port = 90000, + .name = "RTL8239", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info rtl8239c_info = { + .max_mW_per_port = 90000, + .name = "RTL8239C", + .pw_read_lsb_mW = 400, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT, + .pw_set_lsb_mW = 400, +}; + +static const struct rtpse_mcu_chip_info bcm59111_info = { + .max_mW_per_port = 30000, + .name = "BCM59111", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +static const struct rtpse_mcu_chip_info bcm59121_info = { + /* + * BCM59121 is a 60W Type-3 part, but known boards run it at 802.3at + * and the Gen1 dialect has only the 8-bit/0.2W set command (<=51W); + * cap at the 30W the hardware actually offers. + */ + .max_mW_per_port = 30000, + .name = "BCM59121", + .pw_read_lsb_mW = 200, + .pw_set_cmd = RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT, + .pw_set_lsb_mW = 200, +}; + +/* Helpers and basic functions */ + +static struct rtpse_mcu_ctrl *to_rtpse_mcu_ctrl(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct rtpse_mcu_ctrl, pcdev); +} + +static void rtpse_mcu_msg_init(struct rtpse_mcu_msg *msg, u8 opcode) +{ + memset(msg, 0xff, sizeof(*msg)); + msg->opcode = opcode; +} + +static u8 rtpse_mcu_checksum(const u8 *buf, size_t len) +{ + u8 sum = 0; + + while (len--) + sum += *buf++; + return sum; +} + +static int rtpse_mcu_do_xfer(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + unsigned int tries; + int ret; + + for (tries = 0; ; tries++) { + scoped_guard(mutex, &pse->mutex) { + /* Rolling seq_num (skip 0) so a stale/all-zero reply can't match. */ + if (++pse->seq == 0) + pse->seq = 1; + req->seq_num = pse->seq; + req->checksum = rtpse_mcu_checksum((u8 *)req, RTPSE_MCU_MSG_SIZE - 1); + + ret = pse->transport->send(pse, req); + if (ret) + return ret; + + /* Pace the base reply delay; the transport waits its own way. */ + msleep(RTPSE_MCU_RESPONSE_MS); + + memset(resp, 0, sizeof(*resp)); + ret = pse->transport->recv(pse, req, resp); + if (ret) + return ret; + } + + /* NOT_READY: MCU busy, wants the command resent; bounded retry. */ + if (resp->opcode != RTPSE_MCU_OPCODE_NOT_READY || + tries >= RTPSE_MCU_NOT_READY_RETRIES) + break; + msleep(RTPSE_MCU_RESPONSE_MS); + } + + /* Explicit MCU error opcodes (Gen1); map to a meaningful errno. */ + switch (resp->opcode) { + case RTPSE_MCU_OPCODE_INCOMPLETE: + return -EBADE; + case RTPSE_MCU_OPCODE_BAD_CSUM: + return -EBADMSG; + case RTPSE_MCU_OPCODE_NOT_READY: + return -EAGAIN; + } + + if (resp->opcode != req->opcode || + resp->seq_num != req->seq_num || + resp->checksum != rtpse_mcu_checksum((u8 *)resp, RTPSE_MCU_MSG_SIZE - 1)) + return -EBADMSG; + + return 0; +} + +static int rtpse_mcu_port_query(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_msg req; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + + ret = rtpse_mcu_do_xfer(pse, &req, resp); + if (ret) + return ret; + + if (resp->payload[0] != port) + return -EIO; + + return 0; +} + +static int rtpse_mcu_port_cmd(struct rtpse_mcu_ctrl *pse, unsigned int port, u8 opcode, u8 arg) +{ + struct rtpse_mcu_msg req, resp; + int ret; + + rtpse_mcu_msg_init(&req, opcode); + req.payload[0] = port; + req.payload[1] = arg; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + if (resp.payload[0] != port || resp.payload[1] != 0) + return -EIO; + + return 0; +} + +/* Global operations */ + +static int rtpse_mcu_get_info(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_SYSTEM_INFO]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + pse->dialect->parse_system_info(resp.payload, info); + return 0; +} + +static int rtpse_mcu_get_ext_config(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_ext_config *config) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + config->num_of_pses = resp.payload[6]; + + return 0; +} + +static int rtpse_mcu_set_global_state(struct rtpse_mcu_ctrl *pse, bool enable) +{ + struct rtpse_mcu_msg req, resp; + const struct rtpse_mcu_opcode *opc; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_SET_GLOBAL_STATE]; + if (!opc->valid) + return -EOPNOTSUPP; + + rtpse_mcu_msg_init(&req, opc->op); + req.payload[0] = enable ? 0x1 : 0x0; + + ret = rtpse_mcu_do_xfer(pse, &req, &resp); + if (ret) + return ret; + + return (resp.payload[0] == 0x0) ? 0 : -EIO; +} + +/* Port operations */ + +static int rtpse_mcu_port_get_status(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_status *status) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_STATUS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + status->sts1 = resp.payload[1]; + status->sts2 = resp.payload[2]; + status->sts3 = resp.payload[3]; + + return 0; +} + +static int rtpse_mcu_port_get_measurement(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_measurement *measurement) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_POWER_STATS]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + measurement->voltage_raw = get_unaligned_be16(&resp.payload[1]); + measurement->current_raw = get_unaligned_be16(&resp.payload[3]); + measurement->temperature_raw = get_unaligned_be16(&resp.payload[5]); + measurement->power_raw = get_unaligned_be16(&resp.payload[7]); + + return 0; +} + +static int rtpse_mcu_port_get_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->enable = (resp.payload[1] == 1); + + return 0; +} + +static int rtpse_mcu_port_get_ext_config(struct rtpse_mcu_ctrl *pse, unsigned int port, + struct rtpse_mcu_port_ext_config *config) +{ + const struct rtpse_mcu_opcode *opc; + struct rtpse_mcu_msg resp; + int ret; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG]; + if (!opc->valid) + return -EOPNOTSUPP; + + ret = rtpse_mcu_port_query(pse, port, opc->op, &resp); + if (ret) + return ret; + + config->max_power = resp.payload[3]; + config->priority = resp.payload[4]; + + return 0; +} + +static int rtpse_mcu_port_set_state(struct rtpse_mcu_ctrl *pse, unsigned int port, bool enable) +{ + const struct rtpse_mcu_opcode *opc; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_ENABLE]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, port, opc->op, enable ? 0x1 : 0x0); +} + +/* PSE controller ops */ + +static int rtpse_mcu_port_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_config config; + int ret; + + ret = rtpse_mcu_port_get_config(pse, id, &config); + if (ret) + return ret; + + admin_state->c33_admin_state = config.enable ? ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED : + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + return 0; +} + +static int rtpse_mcu_port_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + break; + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_REQUESTING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; + break; + case RTPSE_MCU_PORT_STS_DELIVERING: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + break; + case RTPSE_MCU_PORT_STS_TEST: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_TEST; + break; + case RTPSE_MCU_PORT_STS_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; + break; + case RTPSE_MCU_PORT_STS_OTHER_FAULT: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT; + break; + default: + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN; + break; + } + + return 0; +} + +static int rtpse_mcu_port_get_pw_class(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_status status; + int ret; + + ret = rtpse_mcu_port_get_status(pse, id, &status); + if (ret) + return ret; + + /* + * As per datasheet, the classification result is only valid when in + * one of those operational modes, otherwise not. + */ + switch (status.sts1) { + case RTPSE_MCU_PORT_STS_DISABLED: + case RTPSE_MCU_PORT_STS_SEARCHING: + case RTPSE_MCU_PORT_STS_DELIVERING: + case RTPSE_MCU_PORT_STS_REQUESTING: + return pse->dialect->parse_port_class(&status); + default: + /* + * No class to report, return 0 instead. This is indistinguishable + * from a real class-0 PD but userspace disambiguates via the + * power status. + */ + return 0; + } +} + +static int rtpse_mcu_port_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 100mW per LSB */ + return measurement.power_raw * 100U; +} + +static int rtpse_mcu_port_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_measurement measurement; + int ret; + u32 uV; + + ret = rtpse_mcu_port_get_measurement(pse, id, &measurement); + if (ret) + return ret; + + /* 64.45mV per LSB */ + uV = measurement.voltage_raw * 64450U; + + /* + * Idle ports measure 0V, which the core rejects when turning a power + * limit into a current limit. Fall back to the nominal rail so a limit + * can be set before a PD is attached. + */ + if (!uV) + return RTPSE_MCU_PSE_VOLTAGE_UV; + + return min_t(u32, uV, INT_MAX); +} + +static int rtpse_mcu_port_enable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, true); +} + +static int rtpse_mcu_port_disable(struct pse_controller_dev *pcdev, int id) +{ + return rtpse_mcu_port_set_state(to_rtpse_mcu_ctrl(pcdev), id, false); +} + +static int rtpse_mcu_port_get_pw_limit(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* + * The MCU's raw max_power byte can scale above the chip's rated cap; + * clamp to the same bound set_pw_limit() and the advertised range use. + */ + return min_t(u32, config.max_power * pse->chip->pw_read_lsb_mW, + pse->chip->max_mW_per_port); +} + +static int rtpse_mcu_port_set_pw_limit(struct pse_controller_dev *pcdev, int id, int max_mW) +{ + const struct rtpse_mcu_opcode *type_opc, *val_opc; + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_chip_info *chip = pse->chip; + u8 prg_val; + int ret; + + if (max_mW < 0 || max_mW > chip->max_mW_per_port) + return -ERANGE; + + type_opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE]; + val_opc = &pse->dialect->opcode[chip->pw_set_cmd]; + /* pw_set_lsb_mW is the divisor below; reject a chip that lacks it. */ + if (!type_opc->valid || !val_opc->valid || !chip->pw_set_lsb_mW) + return -EOPNOTSUPP; + + /* + * Round up so a sub-LSB request maps to one LSB, not silently to 0; + * an explicit 0 still yields 0, and LSB-aligned maxima can't overshoot. + */ + prg_val = min_t(unsigned int, DIV_ROUND_UP(max_mW, chip->pw_set_lsb_mW), U8_MAX); + + /* + * Program the value before switching to user-defined mode. The two + * commands aren't atomic, but this order never leaves a stale cap: a + * failure keeps the previous cap, or (already user mode) the requested. + */ + ret = rtpse_mcu_port_cmd(pse, id, val_opc->op, prg_val); + if (ret) + return ret; + + return rtpse_mcu_port_cmd(pse, id, type_opc->op, RTPSE_MCU_PORT_PW_LIMIT_TYPE_USER); +} + +static int rtpse_mcu_port_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *out) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct ethtool_c33_pse_pw_limit_range *range; + + range = kzalloc_obj(*range); + if (!range) + return -ENOMEM; + + range[0].min = 0; + range[0].max = pse->chip->max_mW_per_port; + + out->c33_pw_limit_ranges = range; + return 1; +} + +static int rtpse_mcu_port_get_prio(struct pse_controller_dev *pcdev, int id) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + struct rtpse_mcu_port_ext_config config; + int ret; + + ret = rtpse_mcu_port_get_ext_config(pse, id, &config); + if (ret) + return ret; + + /* Clamp to the advertised max; set_prio() and pis_prio_max use the same bound. */ + return min_t(u8, config.priority, RTPSE_MCU_PORT_MAX_PRIORITY); +} + +static int rtpse_mcu_port_set_prio(struct pse_controller_dev *pcdev, int id, unsigned int prio) +{ + struct rtpse_mcu_ctrl *pse = to_rtpse_mcu_ctrl(pcdev); + const struct rtpse_mcu_opcode *opc; + + if (prio > RTPSE_MCU_PORT_MAX_PRIORITY) + return -ERANGE; + + opc = &pse->dialect->opcode[RTPSE_MCU_CMD_PORT_SET_PRIORITY]; + if (!opc->valid) + return -EOPNOTSUPP; + + return rtpse_mcu_port_cmd(pse, id, opc->op, prio); +} + +static const struct pse_controller_ops rtpse_mcu_ops = { + .pi_get_admin_state = rtpse_mcu_port_get_admin_state, + .pi_get_pw_status = rtpse_mcu_port_get_pw_status, + .pi_get_pw_class = rtpse_mcu_port_get_pw_class, + .pi_get_actual_pw = rtpse_mcu_port_get_actual_pw, + .pi_enable = rtpse_mcu_port_enable, + .pi_disable = rtpse_mcu_port_disable, + .pi_get_voltage = rtpse_mcu_port_get_voltage, + .pi_get_pw_limit = rtpse_mcu_port_get_pw_limit, + .pi_set_pw_limit = rtpse_mcu_port_set_pw_limit, + .pi_get_pw_limit_ranges = rtpse_mcu_port_get_pw_limit_ranges, + .pi_get_prio = rtpse_mcu_port_get_prio, + .pi_set_prio = rtpse_mcu_port_set_prio, +}; + +static int rtpse_mcu_discover(struct rtpse_mcu_ctrl *pse, struct rtpse_mcu_info *info) +{ + struct rtpse_mcu_ext_config ext_config; + unsigned long deadline; + int ret; + + /* + * A booting MCU may stay silent (-ETIMEDOUT), not ACK its address + * (-ENXIO / -EREMOTEIO), report not-ready (-EAGAIN), or emit a + * corrupt/partial frame (-EBADMSG / -EBADE). Retry those within a + * bounded window; other errors (e.g. -EOPNOTSUPP) are fatal and fail + * immediately. + */ + deadline = jiffies + msecs_to_jiffies(RTPSE_MCU_BOOT_TIMEOUT_MS); + do { + ret = rtpse_mcu_get_info(pse, info); + if (ret != -ETIMEDOUT && ret != -ENXIO && ret != -EREMOTEIO && + ret != -EAGAIN && ret != -EBADMSG && ret != -EBADE) + break; + msleep(RTPSE_MCU_BOOT_RETRY_MS); + } while (time_before(jiffies, deadline)); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU info\n"); + + switch (info->device_id) { + case RTPSE_MCU_DEVICE_ID_RTL8238B: + pse->chip = &rtl8238b_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239: + pse->chip = &rtl8239_info; + break; + case RTPSE_MCU_DEVICE_ID_RTL8239C: + pse->chip = &rtl8239c_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59111: + pse->chip = &bcm59111_info; + break; + case RTPSE_MCU_DEVICE_ID_BCM59121: + pse->chip = &bcm59121_info; + break; + default: + return dev_err_probe(pse->dev, -EINVAL, "unknown PSE id 0x%x\n", + info->device_id); + } + + if (!info->max_ports || info->max_ports > RTPSE_MCU_MAX_PORTS) + return dev_err_probe(pse->dev, -EINVAL, + "MCU reports invalid port count %u\n", info->max_ports); + + ret = rtpse_mcu_get_ext_config(pse, &ext_config); + if (ret) + return dev_err_probe(pse->dev, ret, "failed to read MCU ext config\n"); + + dev_info(pse->dev, "%s MCU, %s (id 0x%04x), %u ports across %u PSE chip(s)\n", + pse->dialect->mcu_type_str(info->mcu_type), pse->chip->name, + info->device_id, info->max_ports, ext_config.num_of_pses); + return 0; +} + +static void rtpse_mcu_global_disable(void *data) +{ + struct rtpse_mcu_ctrl *pse = data; + + rtpse_mcu_set_global_state(pse, false); +} + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse) +{ + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_info info; + struct gpio_desc *gpiod; + int ret; + + BUILD_BUG_ON(sizeof(struct rtpse_mcu_msg) != RTPSE_MCU_MSG_SIZE); + + ret = devm_mutex_init(pse->dev, &pse->mutex); + if (ret) + return ret; + + match = device_get_match_data(pse->dev); + if (!match) + return dev_err_probe(pse->dev, -ENODEV, "missing match data\n"); + pse->dialect = match->dialect; + + /* + * Catch a dialect that forgot to set one of the required hooks at + * probe time, rather than NULL-deref'ing later from a fast path. + */ + if (!pse->dialect || + !pse->dialect->parse_system_info || + !pse->dialect->parse_port_class || + !pse->dialect->mcu_type_str) + return dev_err_probe(pse->dev, -EINVAL, + "dialect for chip is incomplete\n"); + + /* + * Release the MCU from reset before the first transaction; the + * boot-retry loop in discover() waits for it to answer. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get reset gpio\n"); + + ret = rtpse_mcu_discover(pse, &info); + if (ret) + return ret; + + /* + * Some boards gate all ports through a hardware line; deassert it only + * after the MCU is confirmed, so a discover failure never ungates the + * ports. It is then left to the MCU - not re-gated on unbind or a later + * probe error - so a driver reload doesn't black out PoE. + */ + gpiod = devm_gpiod_get_optional(pse->dev, "disable-ports", GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(pse->dev, PTR_ERR(gpiod), + "failed to get disable-ports gpio\n"); + + if (!info.system_enable) { + ret = rtpse_mcu_set_global_state(pse, true); + /* Dialects without a global-state concept (e.g. Gen1) return + * -EOPNOTSUPP; treat that as "no separate enable required". + */ + if (ret && ret != -EOPNOTSUPP) + return dev_err_probe(pse->dev, ret, + "failed to enable PSE system\n"); + if (!ret) { + ret = devm_add_action_or_reset(pse->dev, + rtpse_mcu_global_disable, pse); + if (ret) + return ret; + } + } + + /* + * Depending on the MCU firmware configuration (which might be different + * for every board), it isn't known whether the PoE subsystem is active or + * inactive by default. At this stage, the PSE chips might already deliver + * power to PDs without any explicit enable. + */ + + /* pcdev.owner is set by the transport, so the registered controller + * pins the transport module that owns the live device, not the core. + */ + pse->pcdev.ops = &rtpse_mcu_ops; + pse->pcdev.dev = pse->dev; + pse->pcdev.types = ETHTOOL_PSE_C33; + pse->pcdev.nr_lines = info.max_ports; + pse->pcdev.pis_prio_max = RTPSE_MCU_PORT_MAX_PRIORITY; + pse->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC; + + return devm_pse_controller_register(pse->dev, &pse->pcdev); +} +EXPORT_SYMBOL_GPL(rtpse_mcu_register); + +static void rtpse_mcu_gen2_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + info->system_enable = (payload[2] == 0x1); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen2_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Class lives in the upper nibble of sts2. */ + return FIELD_GET(GENMASK(7, 4), status->sts2); +} + +static const char *rtpse_mcu_gen2_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "GigaDevice GD32F310"; + case 0x01: return "GigaDevice GD32F230"; + case 0x02: return "GigaDevice GD32F303"; + case 0x03: return "GigaDevice GD32F103"; + case 0x04: return "GigaDevice GD32E103"; + case 0x10: return "Nuvoton M0516"; + case 0x11: return "Nuvoton M0564"; + case 0x12: return "Nuvoton NUC029"; + default: return "unknown"; + } +} + +static void rtpse_mcu_gen1_parse_system_info(const u8 *payload, struct rtpse_mcu_info *info) +{ + info->max_ports = payload[1]; + /* Gen1 has no explicit system_enable byte; the closest analog is the + * "remote enable" bit in the system-status flags at payload[7]. + */ + info->system_enable = !!(payload[7] & BIT(2)); + info->device_id = get_unaligned_be16(&payload[3]); + info->mcu_type = payload[6]; +} + +static int rtpse_mcu_gen1_parse_port_class(const struct rtpse_mcu_port_status *status) +{ + /* Gen1 puts the detected class in payload[3] (== sts3) directly. + * Mask to the low nibble; class is 0..8 and any high bits would be + * noise. + */ + return status->sts3 & 0x0f; +} + +static const char *rtpse_mcu_gen1_mcu_type_str(unsigned int mcu_type) +{ + switch (mcu_type) { + case 0x00: return "ST Micro ST32F100"; + case 0x01: return "Nuvoton M05xx LAN"; + case 0x02: return "ST Micro STF030C8"; + case 0x03: return "Nuvoton M058SAN"; + case 0x04: return "Nuvoton NUC122"; + default: return "unknown"; + } +} + +/* Map each logical command the core issues to its per-dialect opcode. */ +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen2 = { + .parse_system_info = rtpse_mcu_gen2_parse_system_info, + .parse_port_class = rtpse_mcu_gen2_parse_port_class, + .mcu_type_str = rtpse_mcu_gen2_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_SET_GLOBAL_STATE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x40), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x4a), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x01), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x12), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x13), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_EXT] = RTPSE_MCU_OP(0x14), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x42), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x44), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x48), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x49), + }, +}; + +static const struct rtpse_mcu_dialect rtpse_mcu_dialect_gen1 = { + .parse_system_info = rtpse_mcu_gen1_parse_system_info, + .parse_port_class = rtpse_mcu_gen1_parse_port_class, + .mcu_type_str = rtpse_mcu_gen1_mcu_type_str, + .opcode = { + [RTPSE_MCU_CMD_GET_SYSTEM_INFO] = RTPSE_MCU_OP(0x20), + [RTPSE_MCU_CMD_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x2b), + + [RTPSE_MCU_CMD_PORT_ENABLE] = RTPSE_MCU_OP(0x00), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT_TYPE] = RTPSE_MCU_OP(0x15), + [RTPSE_MCU_CMD_PORT_SET_POWER_LIMIT] = RTPSE_MCU_OP(0x16), + [RTPSE_MCU_CMD_PORT_SET_PRIORITY] = RTPSE_MCU_OP(0x1a), + [RTPSE_MCU_CMD_PORT_GET_STATUS] = RTPSE_MCU_OP(0x21), + [RTPSE_MCU_CMD_PORT_GET_POWER_STATS] = RTPSE_MCU_OP(0x30), + [RTPSE_MCU_CMD_PORT_GET_CONFIG] = RTPSE_MCU_OP(0x25), + [RTPSE_MCU_CMD_PORT_GET_EXT_CONFIG] = RTPSE_MCU_OP(0x26), + }, +}; + +const struct rtpse_mcu_match_data rtpse_mcu_gen1_data = { + .dialect = &rtpse_mcu_dialect_gen1, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen1_data); + +const struct rtpse_mcu_match_data rtpse_mcu_gen2_data = { + .dialect = &rtpse_mcu_dialect_gen2, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_data); + +/* Same dialect as gen2, but the MCU expects raw-I2C framing. */ +const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data = { + .dialect = &rtpse_mcu_dialect_gen2, + .native_i2c = true, +}; +EXPORT_SYMBOL_GPL(rtpse_mcu_gen2_i2c_data); + +MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>"); +MODULE_DESCRIPTION("Realtek PSE MCU driver (core)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu-i2c.c b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c new file mode 100644 index 000000000..b6769ac1d --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-i2c.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pse-pd/pse.h> + +#include "realtek-pse-mcu.h" + +/* + * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so + * the response is normally ready on the very first read. For commands the + * MCU produces more slowly, keep polling at the typical response cadence + * up to the worst-case ceiling. + */ +#define RTPSE_MCU_I2C_RETRY_MS RTPSE_MCU_RESPONSE_MS +#define RTPSE_MCU_I2C_MAX_TRIES (RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS) + +static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + + /* Send opcode as SMBus command byte; remaining 11 bytes as block data */ + return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1, + (const u8 *)req + 1); +} + +static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + /* MCU needs 0x00 as command byte for read */ + ret = i2c_smbus_read_i2c_block_data(client, 0x00, + RTPSE_MCU_MSG_SIZE, + (u8 *)resp); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = { + .send = rtpse_mcu_i2c_smbus_send, + .recv = rtpse_mcu_i2c_smbus_recv, +}; + +static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int ret; + + ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO; +} + +static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct i2c_client *client = to_i2c_client(pse->dev); + int tries, ret; + + for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) { + if (tries > 0) + msleep(RTPSE_MCU_I2C_RETRY_MS); + + ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE); + if (ret < 0) + return ret; + if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = { + .send = rtpse_mcu_i2c_native_send, + .recv = rtpse_mcu_i2c_native_recv, +}; + +static int rtpse_mcu_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct rtpse_mcu_match_data *match; + struct rtpse_mcu_ctrl *pse; + bool use_native; + + match = device_get_match_data(dev); + if (!match) + return dev_err_probe(dev, -ENODEV, "missing match data\n"); + + /* The framing (raw I2C vs SMBus) is carried by the match data. */ + use_native = match->native_i2c; + if (use_native) { + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EOPNOTSUPP, + "plain-I2C MCU protocol requires I2C-capable adapter\n"); + } else { + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return dev_err_probe(dev, -EOPNOTSUPP, + "SMBus MCU protocol requires SMBus I2C-block support\n"); + } + + pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL); + if (!pse) + return -ENOMEM; + + pse->dev = dev; + pse->pcdev.owner = THIS_MODULE; + pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops; + + return rtpse_mcu_register(pse); +} + +static const struct of_device_id rtpse_mcu_i2c_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data }, + { .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match); + +static struct i2c_driver rtpse_mcu_i2c_driver = { + .driver = { + .name = "realtek-pse-mcu-i2c", + .of_match_table = rtpse_mcu_i2c_of_match, + }, + .probe = rtpse_mcu_i2c_probe, +}; +module_i2c_driver(rtpse_mcu_i2c_driver); + +MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>"); +MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu-uart.c b/drivers/net/pse-pd/realtek-pse-mcu-uart.c new file mode 100644 index 000000000..9baa17d8d --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu-uart.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pse-pd/pse.h> +#include <linux/serdev.h> +#include <linux/spinlock.h> +#include <linux/string.h> + +#include "realtek-pse-mcu.h" + +#define RTPSE_MCU_UART_BAUD_DEFAULT 19200 +#define RTPSE_MCU_UART_TX_TIMEOUT msecs_to_jiffies(100) +#define RTPSE_MCU_UART_RX_TIMEOUT msecs_to_jiffies(RTPSE_MCU_RESPONSE_MAX_MS) + +struct rtpse_mcu_uart { + struct rtpse_mcu_ctrl pse; + struct serdev_device *serdev; + struct completion rx_done; + spinlock_t rx_lock; /* protects rx_buf and rx_len */ + size_t rx_len; + u8 rx_buf[RTPSE_MCU_MSG_SIZE]; +}; + +#define to_rtpse_mcu_uart(p) container_of(p, struct rtpse_mcu_uart, pse) + +/* + * No framing is done here: a glitched frame costs one transaction, then + * the next _send re-frames from rx_len 0. Resync works by returning count + * (not take), dropping any overflow so serdev keeps no leftover to bleed + * into the next frame. + */ +static size_t rtpse_mcu_uart_receive(struct serdev_device *serdev, + const u8 *buf, size_t count) +{ + struct rtpse_mcu_uart *ctx = serdev_device_get_drvdata(serdev); + size_t take; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + take = min(count, sizeof(ctx->rx_buf) - ctx->rx_len); + if (take) { + memcpy(ctx->rx_buf + ctx->rx_len, buf, take); + ctx->rx_len += take; + if (ctx->rx_len == sizeof(ctx->rx_buf)) + complete(&ctx->rx_done); + } + } + + /* consume all to avoid desync/misalignment */ + return count; +} + +static const struct serdev_device_ops rtpse_mcu_uart_serdev_ops = { + .receive_buf = rtpse_mcu_uart_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int rtpse_mcu_uart_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + int written; + + /* clear any leftover rx state before transmitting */ + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + reinit_completion(&ctx->rx_done); + ctx->rx_len = 0; + } + + written = serdev_device_write(ctx->serdev, (const u8 *)req, sizeof(*req), + RTPSE_MCU_UART_TX_TIMEOUT); + if (written < 0) + return written; + if (written != sizeof(*req)) + return -EIO; + + return 0; +} + +static int rtpse_mcu_uart_recv(struct rtpse_mcu_ctrl *pse, + const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp) +{ + struct rtpse_mcu_uart *ctx = to_rtpse_mcu_uart(pse); + + if (!wait_for_completion_timeout(&ctx->rx_done, RTPSE_MCU_UART_RX_TIMEOUT)) + return -ETIMEDOUT; + + scoped_guard(spinlock_irqsave, &ctx->rx_lock) { + if (ctx->rx_len != sizeof(*resp)) + return -EIO; + + memcpy(resp, ctx->rx_buf, sizeof(*resp)); + } + return 0; +} + +static const struct rtpse_mcu_transport_ops rtpse_mcu_uart_transport_ops = { + .send = rtpse_mcu_uart_send, + .recv = rtpse_mcu_uart_recv, +}; + +static int rtpse_mcu_uart_probe(struct serdev_device *serdev) +{ + u32 speed = RTPSE_MCU_UART_BAUD_DEFAULT; + struct device *dev = &serdev->dev; + struct rtpse_mcu_uart *ctx; + unsigned int baud; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->serdev = serdev; + ctx->pse.dev = dev; + ctx->pse.pcdev.owner = THIS_MODULE; + ctx->pse.transport = &rtpse_mcu_uart_transport_ops; + init_completion(&ctx->rx_done); + spin_lock_init(&ctx->rx_lock); + + serdev_device_set_drvdata(serdev, ctx); + serdev_device_set_client_ops(serdev, &rtpse_mcu_uart_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "failed to open serdev\n"); + + fwnode_property_read_u32(dev_fwnode(dev), "current-speed", &speed); + + baud = serdev_device_set_baudrate(serdev, speed); + if (baud != speed) + dev_warn(dev, "could not set baudrate %u, controller uses %u\n", + speed, baud); + + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + dev_warn(dev, "could not set parity to none: %d\n", ret); + + return rtpse_mcu_register(&ctx->pse); +} + +static const struct of_device_id rtpse_mcu_uart_of_match[] = { + { .compatible = "realtek,pse-mcu-gen1", .data = &rtpse_mcu_gen1_data }, + { .compatible = "realtek,pse-mcu-gen2", .data = &rtpse_mcu_gen2_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rtpse_mcu_uart_of_match); + +static struct serdev_device_driver rtpse_mcu_uart_driver = { + .driver = { + .name = "realtek-pse-mcu-uart", + .of_match_table = rtpse_mcu_uart_of_match, + }, + .probe = rtpse_mcu_uart_probe, +}; +module_serdev_device_driver(rtpse_mcu_uart_driver); + +MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>"); +MODULE_DESCRIPTION("Realtek PSE MCU driver (UART transport)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/realtek-pse-mcu.h b/drivers/net/pse-pd/realtek-pse-mcu.h new file mode 100644 index 000000000..d75e91669 --- /dev/null +++ b/drivers/net/pse-pd/realtek-pse-mcu.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _REALTEK_PSE_MCU_H +#define _REALTEK_PSE_MCU_H + +#include <linux/mutex.h> +#include <linux/pse-pd/pse.h> +#include <linux/types.h> + +/* + * Time the MCU itself needs between accepting a request and having a + * response ready. These are properties of the MCU firmware, not of the + * underlying transport: the core paces transactions by RTPSE_MCU_RESPONSE_MS + * and both transports size their per-transaction recv ceiling from + * RTPSE_MCU_RESPONSE_MAX_MS, since some commands are documented as + * needing up to ~1s to produce a reply. + */ +#define RTPSE_MCU_RESPONSE_MS 25 +#define RTPSE_MCU_RESPONSE_MAX_MS 1000 + +/* + * Total time to keep retrying the first MCU read at probe, and the pause + * between attempts. Right after reset-gpios is deasserted the MCU may not + * answer on the bus yet; give it a bounded window to come up before + * declaring the probe failed. + */ +#define RTPSE_MCU_BOOT_TIMEOUT_MS 3000 +#define RTPSE_MCU_BOOT_RETRY_MS 100 + +#define RTPSE_MCU_MSG_SIZE 12 + +struct rtpse_mcu_msg { + u8 opcode; + u8 seq_num; + u8 payload[9]; + u8 checksum; +} __packed; + +/* + * MCU status opcodes (seen on the Gen1 dialect; Gen2 never emits them). + * INCOMPLETE/BAD_CSUM are terminal; NOT_READY is transient. + */ +#define RTPSE_MCU_OPCODE_INCOMPLETE 0xfd /* -EBADE */ +#define RTPSE_MCU_OPCODE_BAD_CSUM 0xfe /* -EBADMSG */ +#define RTPSE_MCU_OPCODE_NOT_READY 0xff /* -EAGAIN */ + +/* + * A polling transport can stop here: the reply to this request (opcode and + * seq_num) or a terminal error. The seq_num rejects a stale normal reply; the + * terminal errors match unconditionally, as a request the MCU couldn't parse + * carries no seq_num to correlate against. + */ +static inline bool rtpse_mcu_resp_is_final(const struct rtpse_mcu_msg *req, + const struct rtpse_mcu_msg *resp) +{ + return (resp->opcode == req->opcode && resp->seq_num == req->seq_num) || + resp->opcode == RTPSE_MCU_OPCODE_INCOMPLETE || + resp->opcode == RTPSE_MCU_OPCODE_BAD_CSUM; +} + +/* Opaque to transports; defined in realtek-pse-mcu-core.c. */ +struct rtpse_mcu_dialect; +struct rtpse_mcu_chip_info; +struct rtpse_mcu_ctrl; + +/* Per-compatible match data (the of_match .data). */ +struct rtpse_mcu_match_data { + const struct rtpse_mcu_dialect *dialect; + bool native_i2c; /* raw-I2C framing (vs SMBus); I2C transport only */ +}; + +struct rtpse_mcu_transport_ops { + int (*send)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req); + int (*recv)(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req, + struct rtpse_mcu_msg *resp); +}; + +struct rtpse_mcu_ctrl { + struct device *dev; + struct pse_controller_dev pcdev; + struct mutex mutex; /* serializes MCU request/response transactions */ + const struct rtpse_mcu_dialect *dialect; + const struct rtpse_mcu_chip_info *chip; + const struct rtpse_mcu_transport_ops *transport; + u8 seq; /* rolling request seq_num, echoed by the MCU */ +}; + +int rtpse_mcu_register(struct rtpse_mcu_ctrl *pse); + +extern const struct rtpse_mcu_match_data rtpse_mcu_gen1_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_data; +extern const struct rtpse_mcu_match_data rtpse_mcu_gen2_i2c_data; + +#endif diff --git a/drivers/net/pse-pd/si3474.c b/drivers/net/pse-pd/si3474.c new file mode 100644 index 000000000..1845b9c51 --- /dev/null +++ b/drivers/net/pse-pd/si3474.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Skyworks Si3474 PoE PSE Controller + * + * Chip Architecture & Terminology: + * + * The Si3474 is a single-chip PoE PSE controller managing 8 physical power + * delivery channels. Internally, it's structured into two logical "Quads". + * + * Quad 0: Manages physical channels ('ports' in datasheet) 0, 1, 2, 3 + * Quad 1: Manages physical channels ('ports' in datasheet) 4, 5, 6, 7 + * + * Each Quad is accessed via a separate I2C address. The base address range is + * set by hardware pins A1-A4, and the specific address selects Quad 0 (usually + * the lower/even address) or Quad 1 (usually the higher/odd address). + * See datasheet Table 2.2 for the address mapping. + * + * While the Quads manage channel-specific operations, the Si3474 package has + * several resources shared across the entire chip: + * - Single RESETb input pin. + * - Single INTb output pin (signals interrupts from *either* Quad). + * - Single OSS input pin (Emergency Shutdown). + * - Global I2C Address (0x7F) used for firmware updates. + * - Global status monitoring (Temperature, VDD/VPWR Undervoltage Lockout). + * + * Driver Architecture: + * + * To handle the mix of per-Quad access and shared resources correctly, this + * driver treats the entire Si3474 package as one logical device. The driver + * instance associated with the primary I2C address (Quad 0) takes ownership. + * It discovers and manages the I2C client for the secondary address (Quad 1). + * This primary instance handles shared resources like IRQ management and + * registers a single PSE controller device representing all logical PIs. + * Internal functions route I2C commands to the appropriate Quad's i2c_client + * based on the target channel or PI. + * + * Terminology Mapping: + * + * - "PI" (Power Interface): Refers to the logical PSE port as defined by + * IEEE 802.3 (typically corresponds to an RJ45 connector). This is the + * `id` (0-7) used in the pse_controller_ops. + * - "Channel": Refers to one of the 8 physical power control paths within + * the Si3474 chip itself (hardware channels 0-7). This terminology is + * used internally within the driver to avoid confusion with 'ports'. + * - "Quad": One of the two internal 4-channel management units within the + * Si3474, each accessed via its own I2C address. + * + * Relationship: + * - A 2-Pair PoE PI uses 1 Channel. + * - A 4-Pair PoE PI uses 2 Channels. + * + * ASCII Schematic: + * + * +-----------------------------------------------------+ + * | Si3474 Chip | + * | | + * | +---------------------+ +---------------------+ | + * | | Quad 0 | | Quad 1 | | + * | | Channels 0, 1, 2, 3 | | Channels 4, 5, 6, 7 | | + * | +----------^----------+ +-------^-------------+ | + * | I2C Addr 0 | | I2C Addr 1 | + * | +------------------------+ | + * | (Primary Driver Instance) (Managed by Primary) | + * | | + * | Shared Resources (affect whole chip): | + * | - Single INTb Output -> Handled by Primary | + * | - Single RESETb Input | + * | - Single OSS Input -> Handled by Primary | + * | - Global I2C Addr (0x7F) for Firmware Update | + * | - Global Status (Temp, VDD/VPWR UVLO) | + * +-----------------------------------------------------+ + * | | | | | | | | + * Ch0 Ch1 Ch2 Ch3 Ch4 Ch5 Ch6 Ch7 (Physical Channels) + * + * Example Mapping (Logical PI to Physical Channel(s)): + * * 2-Pair Mode (8 PIs): + * PI 0 -> Ch 0 + * PI 1 -> Ch 1 + * ... + * PI 7 -> Ch 7 + * * 4-Pair Mode (4 PIs): + * PI 0 -> Ch 0 + Ch 1 (Managed via Quad 0 Addr) + * PI 1 -> Ch 2 + Ch 3 (Managed via Quad 0 Addr) + * PI 2 -> Ch 4 + Ch 5 (Managed via Quad 1 Addr) + * PI 3 -> Ch 6 + Ch 7 (Managed via Quad 1 Addr) + * (Note: Actual mapping depends on Device Tree and PORT_REMAP config) + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pse-pd/pse.h> + +#define SI3474_MAX_CHANS 8 + +#define MANUFACTURER_ID 0x08 +#define IC_ID 0x05 +#define SI3474_DEVICE_ID (MANUFACTURER_ID << 3 | IC_ID) + +/* Misc registers */ +#define VENDOR_IC_ID_REG 0x1B +#define TEMPERATURE_REG 0x2C +#define FIRMWARE_REVISION_REG 0x41 +#define CHIP_REVISION_REG 0x43 + +/* Main status registers */ +#define POWER_STATUS_REG 0x10 +#define PORT_MODE_REG 0x12 +#define DETECT_CLASS_ENABLE_REG 0x14 + +/* PORTn Current */ +#define PORT1_CURRENT_LSB_REG 0x30 + +/* PORTn Current [mA], return in [nA] */ +/* 1000 * ((PORTn_CURRENT_MSB << 8) + PORTn_CURRENT_LSB) / 16384 */ +#define SI3474_NA_STEP (1000 * 1000 * 1000 / 16384) + +/* VPWR Voltage */ +#define VPWR_LSB_REG 0x2E +#define VPWR_MSB_REG 0x2F + +/* PORTn Voltage */ +#define PORT1_VOLTAGE_LSB_REG 0x32 + +/* VPWR Voltage [V], return in [uV] */ +/* 60 * (( VPWR_MSB << 8) + VPWR_LSB) / 16384 */ +#define SI3474_UV_STEP (1000 * 1000 * 60 / 16384) + +/* Helper macros */ +#define CHAN_IDX(chan) ((chan) % 4) +#define CHAN_BIT(chan) BIT(CHAN_IDX(chan)) +#define CHAN_UPPER_BIT(chan) BIT(CHAN_IDX(chan) + 4) + +#define CHAN_MASK(chan) (0x03U << (2 * CHAN_IDX(chan))) +#define CHAN_REG(base, chan) ((base) + (CHAN_IDX(chan) * 4)) + +struct si3474_pi_desc { + u8 chan[2]; + bool is_4p; +}; + +struct si3474_priv { + struct i2c_client *client[2]; + struct pse_controller_dev pcdev; + struct device_node *np; + struct si3474_pi_desc pi[SI3474_MAX_CHANS]; +}; + +static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct si3474_priv, pcdev); +} + +static void si3474_get_channels(struct si3474_priv *priv, int id, + u8 *chan0, u8 *chan1) +{ + *chan0 = priv->pi[id].chan[0]; + *chan1 = priv->pi[id].chan[1]; +} + +static struct i2c_client *si3474_get_chan_client(struct si3474_priv *priv, + u8 chan) +{ + return (chan < 4) ? priv->client[0] : priv->client[1]; +} + +static int si3474_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + struct i2c_client *client; + bool is_enabled; + u8 chan0, chan1; + s32 ret; + + si3474_get_channels(priv, id, &chan0, &chan1); + client = si3474_get_chan_client(priv, chan0); + + ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG); + if (ret < 0) { + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN; + return ret; + } + + is_enabled = ret & (CHAN_MASK(chan0) | CHAN_MASK(chan1)); + + if (is_enabled) + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + + return 0; +} + +static int si3474_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + struct i2c_client *client; + bool delivering; + u8 chan0, chan1; + s32 ret; + + si3474_get_channels(priv, id, &chan0, &chan1); + client = si3474_get_chan_client(priv, chan0); + + ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG); + if (ret < 0) { + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN; + return ret; + } + + delivering = ret & (CHAN_UPPER_BIT(chan0) | CHAN_UPPER_BIT(chan1)); + + if (delivering) + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + else + pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + + return 0; +} + +static int si3474_get_of_channels(struct si3474_priv *priv) +{ + struct pse_pi *pi; + u32 chan_id; + u8 pi_no; + s32 ret; + + for (pi_no = 0; pi_no < SI3474_MAX_CHANS; pi_no++) { + pi = &priv->pcdev.pi[pi_no]; + bool pairset_found = false; + u8 pairset_no; + + for (pairset_no = 0; pairset_no < 2; pairset_no++) { + if (!pi->pairset[pairset_no].np) + continue; + + pairset_found = true; + + ret = of_property_read_u32(pi->pairset[pairset_no].np, + "reg", &chan_id); + if (ret) { + dev_err(&priv->client[0]->dev, + "Failed to read channel reg property\n"); + return ret; + } + if (chan_id > SI3474_MAX_CHANS) { + dev_err(&priv->client[0]->dev, + "Incorrect channel number: %d\n", chan_id); + return -EINVAL; + } + + priv->pi[pi_no].chan[pairset_no] = chan_id; + /* Mark as 4-pair if second pairset is present */ + priv->pi[pi_no].is_4p = (pairset_no == 1); + } + + if (pairset_found && !priv->pi[pi_no].is_4p) { + dev_err(&priv->client[0]->dev, + "Second pairset is missing for PI %pOF, only 4p configs are supported\n", + pi->np); + return -EINVAL; + } + } + + return 0; +} + +static int si3474_setup_pi_matrix(struct pse_controller_dev *pcdev) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + s32 ret; + + ret = si3474_get_of_channels(priv); + if (ret < 0) + dev_warn(&priv->client[0]->dev, + "Unable to parse DT PSE power interface matrix\n"); + + return ret; +} + +static int si3474_pi_enable(struct pse_controller_dev *pcdev, int id) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + struct i2c_client *client; + u8 chan0, chan1; + s32 ret; + u8 val; + + si3474_get_channels(priv, id, &chan0, &chan1); + client = si3474_get_chan_client(priv, chan0); + + /* Release PI from shutdown */ + ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG); + if (ret < 0) + return ret; + + val = (u8)ret; + val |= CHAN_MASK(chan0); + val |= CHAN_MASK(chan1); + + ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val); + if (ret) + return ret; + + /* DETECT_CLASS_ENABLE must be set when using AUTO mode, + * otherwise PI does not power up - datasheet section 2.10.2 + */ + val = CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) | + CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1); + + ret = i2c_smbus_write_byte_data(client, DETECT_CLASS_ENABLE_REG, val); + if (ret) + return ret; + + return 0; +} + +static int si3474_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + struct i2c_client *client; + u8 chan0, chan1; + s32 ret; + u8 val; + + si3474_get_channels(priv, id, &chan0, &chan1); + client = si3474_get_chan_client(priv, chan0); + + /* Set PI in shutdown mode */ + ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG); + if (ret < 0) + return ret; + + val = (u8)ret; + val &= ~CHAN_MASK(chan0); + val &= ~CHAN_MASK(chan1); + + ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val); + if (ret) + return ret; + + return 0; +} + +static int si3474_pi_get_chan_current(struct si3474_priv *priv, u8 chan) +{ + struct i2c_client *client; + u64 tmp_64; + s32 ret; + u8 reg; + + client = si3474_get_chan_client(priv, chan); + + /* Registers 0x30 to 0x3d */ + reg = CHAN_REG(PORT1_CURRENT_LSB_REG, chan); + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + tmp_64 = ret * SI3474_NA_STEP; + + /* uA = nA / 1000 */ + tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000); + return (int)tmp_64; +} + +static int si3474_pi_get_chan_voltage(struct si3474_priv *priv, u8 chan) +{ + struct i2c_client *client; + s32 ret; + u32 val; + u8 reg; + + client = si3474_get_chan_client(priv, chan); + + /* Registers 0x32 to 0x3f */ + reg = CHAN_REG(PORT1_VOLTAGE_LSB_REG, chan); + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + val = ret * SI3474_UV_STEP; + + return (int)val; +} + +static int si3474_pi_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + struct i2c_client *client; + u8 chan0, chan1; + s32 ret; + + si3474_get_channels(priv, id, &chan0, &chan1); + client = si3474_get_chan_client(priv, chan0); + + /* Check which channels are enabled*/ + ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG); + if (ret < 0) + return ret; + + /* Take voltage from the first enabled channel */ + if (ret & CHAN_BIT(chan0)) + ret = si3474_pi_get_chan_voltage(priv, chan0); + else if (ret & CHAN_BIT(chan1)) + ret = si3474_pi_get_chan_voltage(priv, chan1); + else + /* 'should' be no voltage in this case */ + return 0; + + return ret; +} + +static int si3474_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct si3474_priv *priv = to_si3474_priv(pcdev); + u8 chan0, chan1; + u32 uV, uA; + u64 tmp_64; + s32 ret; + + ret = si3474_pi_get_voltage(&priv->pcdev, id); + + /* Do not read currents if voltage is 0 */ + if (ret <= 0) + return ret; + uV = ret; + + si3474_get_channels(priv, id, &chan0, &chan1); + + ret = si3474_pi_get_chan_current(priv, chan0); + if (ret < 0) + return ret; + uA = ret; + + ret = si3474_pi_get_chan_current(priv, chan1); + if (ret < 0) + return ret; + uA += ret; + + tmp_64 = uV; + tmp_64 *= uA; + /* mW = uV * uA / 1000000000 */ + return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); +} + +static const struct pse_controller_ops si3474_ops = { + .setup_pi_matrix = si3474_setup_pi_matrix, + .pi_enable = si3474_pi_enable, + .pi_disable = si3474_pi_disable, + .pi_get_actual_pw = si3474_pi_get_actual_pw, + .pi_get_voltage = si3474_pi_get_voltage, + .pi_get_admin_state = si3474_pi_get_admin_state, + .pi_get_pw_status = si3474_pi_get_pw_status, +}; + +static void si3474_ancillary_i2c_remove(void *data) +{ + struct i2c_client *client = data; + + i2c_unregister_device(client); +} + +static int si3474_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct si3474_priv *priv; + u8 fw_version; + s32 ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "i2c check functionality failed\n"); + return -ENXIO; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = i2c_smbus_read_byte_data(client, VENDOR_IC_ID_REG); + if (ret < 0) + return ret; + + if (ret != SI3474_DEVICE_ID) { + dev_err(dev, "Wrong device ID: 0x%x\n", ret); + return -ENXIO; + } + + ret = i2c_smbus_read_byte_data(client, FIRMWARE_REVISION_REG); + if (ret < 0) + return ret; + fw_version = ret; + + ret = i2c_smbus_read_byte_data(client, CHIP_REVISION_REG); + if (ret < 0) + return ret; + + dev_dbg(dev, "Chip revision: 0x%x, firmware version: 0x%x\n", + ret, fw_version); + + priv->client[0] = client; + i2c_set_clientdata(client, priv); + + priv->client[1] = i2c_new_ancillary_device(priv->client[0], "secondary", + priv->client[0]->addr + 1); + if (IS_ERR(priv->client[1])) + return PTR_ERR(priv->client[1]); + + ret = devm_add_action_or_reset(dev, si3474_ancillary_i2c_remove, priv->client[1]); + if (ret < 0) { + dev_err(&priv->client[1]->dev, "Cannot register remove callback\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(priv->client[1], VENDOR_IC_ID_REG); + if (ret < 0) { + dev_err(&priv->client[1]->dev, "Cannot access secondary PSE controller\n"); + return ret; + } + + if (ret != SI3474_DEVICE_ID) { + dev_err(&priv->client[1]->dev, + "Wrong device ID for secondary PSE controller: 0x%x\n", ret); + return -ENXIO; + } + + priv->np = dev->of_node; + priv->pcdev.owner = THIS_MODULE; + priv->pcdev.ops = &si3474_ops; + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_C33; + priv->pcdev.nr_lines = SI3474_MAX_CHANS; + + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) { + dev_err(dev, "Failed to register PSE controller: 0x%x\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id si3474_id[] = { + { .name = "si3474" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si3474_id); + +static const struct of_device_id si3474_of_match[] = { + { + .compatible = "skyworks,si3474", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, si3474_of_match); + +static struct i2c_driver si3474_driver = { + .probe = si3474_i2c_probe, + .id_table = si3474_id, + .driver = { + .name = "si3474", + .of_match_table = si3474_of_match, + }, +}; +module_i2c_driver(si3474_driver); + +MODULE_AUTHOR("Piotr Kubik <piotr.kubik@adtran.com>"); +MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c new file mode 100644 index 000000000..8a3bbd623 --- /dev/null +++ b/drivers/net/pse-pd/tps23881.c @@ -0,0 +1,1558 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the TI TPS23881 PoE PSE Controller driver (I2C bus) + * + * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com> + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pse-pd/pse.h> + +#define TPS23881_MAX_CHANS 8 +#define TPS23881_MAX_IRQ_RETRIES 10 + +#define TPS23881_REG_IT 0x0 +#define TPS23881_REG_IT_MASK 0x1 +#define TPS23881_REG_IT_DISF BIT(2) +#define TPS23881_REG_IT_DETC BIT(3) +#define TPS23881_REG_IT_CLASC BIT(4) +#define TPS23881_REG_IT_IFAULT BIT(5) +#define TPS23881_REG_IT_SUPF BIT(7) +#define TPS23881_REG_DET_EVENT 0x5 +#define TPS23881_REG_FAULT 0x7 +#define TPS23881_REG_SUPF_EVENT 0xb +#define TPS23881_REG_TSD BIT(7) +#define TPS23881_REG_DISC 0xc +#define TPS23881_REG_PW_STATUS 0x10 +#define TPS23881_REG_OP_MODE 0x12 +#define TPS23881_REG_DISC_EN 0x13 +#define TPS23881_OP_MODE_SEMIAUTO 0xaaaa +#define TPS23881_REG_DIS_EN 0x13 +#define TPS23881_REG_DET_CLA_EN 0x14 +#define TPS23881_REG_GEN_MASK 0x17 +#define TPS23881_REG_CLCHE BIT(2) +#define TPS23881_REG_DECHE BIT(3) +#define TPS23881_REG_NBITACC BIT(5) +#define TPS23881_REG_INTEN BIT(7) +#define TPS23881_REG_PW_EN 0x19 +#define TPS23881_REG_RESET 0x1a +#define TPS23881_REG_CLRAIN BIT(7) +#define TPS23881_REG_2PAIR_POL1 0x1e +#define TPS23881_REG_PORT_MAP 0x26 +#define TPS23881_REG_PORT_POWER 0x29 +#define TPS23881_REG_4PAIR_POL1 0x2a +#define TPS23881_REG_INPUT_V 0x2e +#define TPS23881_REG_CHAN1_A 0x30 +#define TPS23881_REG_CHAN1_V 0x32 +#define TPS23881_REG_FOLDBACK 0x40 +#define TPS23881_REG_TPON BIT(0) +#define TPS23881_REG_FWREV 0x41 +#define TPS23881_REG_DEVID 0x43 +#define TPS23881_REG_CHAN1_CLASS 0x4c +#define TPS23881_REG_SRAM_CTRL 0x60 +#define TPS23881_REG_SRAM_DATA 0x61 + +#define TPS23881_UV_STEP 3662 +#define TPS23881_NA_STEP 89500 +#define TPS23881_MW_STEP 500 +#define TPS23881_MIN_PI_PW_LIMIT_MW 2000 + +struct tps23881_port_desc { + u8 chan[2]; + bool is_4p; + int pw_pol; + bool exist; +}; + +struct tps23881_priv { + struct i2c_client *client; + struct pse_controller_dev pcdev; + struct device_node *np; + struct tps23881_port_desc port[TPS23881_MAX_CHANS]; +}; + +static struct tps23881_priv *to_tps23881_priv(struct pse_controller_dev *pcdev) +{ + return container_of(pcdev, struct tps23881_priv, pcdev); +} + +/* + * Helper to extract a value from a u16 register value, which is made of two + * u8 registers. The function calculates the bit offset based on the channel + * and extracts the relevant bits using a provided field mask. + * + * @param reg_val: The u16 register value (composed of two u8 registers). + * @param chan: The channel number (0-7). + * @param field_offset: The base bit offset to apply (e.g., 0 or 4). + * @param field_mask: The mask to apply to extract the required bits. + * @return: The extracted value for the specific channel. + */ +static u16 tps23881_calc_val(u16 reg_val, u8 chan, u8 field_offset, + u16 field_mask) +{ + if (chan >= 4) + reg_val >>= 8; + + return (reg_val >> field_offset) & field_mask; +} + +/* + * Helper to combine individual channel values into a u16 register value. + * The function sets the value for a specific channel in the appropriate + * position. + * + * @param reg_val: The current u16 register value. + * @param chan: The channel number (0-7). + * @param field_offset: The base bit offset to apply (e.g., 0 or 4). + * @param field_mask: The mask to apply for the field (e.g., 0x0F). + * @param field_val: The value to set for the specific channel (masked by + * field_mask). + * @return: The updated u16 register value with the channel value set. + */ +static u16 tps23881_set_val(u16 reg_val, u8 chan, u8 field_offset, + u16 field_mask, u16 field_val) +{ + field_val &= field_mask; + + if (chan < 4) { + reg_val &= ~(field_mask << field_offset); + reg_val |= (field_val << field_offset); + } else { + reg_val &= ~(field_mask << (field_offset + 8)); + reg_val |= (field_val << (field_offset + 8)); + } + + return reg_val; +} + +static int +tps23881_pi_set_pw_pol_limit(struct tps23881_priv *priv, int id, u8 pw_pol, + bool is_4p) +{ + struct i2c_client *client = priv->client; + int ret, reg; + u16 val; + u8 chan; + + chan = priv->port[id].chan[0]; + if (!is_4p) { + reg = TPS23881_REG_2PAIR_POL1 + (chan % 4); + } else { + /* One chan is enough to configure the 4p PI power limit */ + if ((chan % 4) < 2) + reg = TPS23881_REG_4PAIR_POL1; + else + reg = TPS23881_REG_4PAIR_POL1 + 1; + } + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + val = tps23881_set_val(ret, chan, 0, 0xff, pw_pol); + return i2c_smbus_write_word_data(client, reg, val); +} + +static int tps23881_pi_enable_manual_pol(struct tps23881_priv *priv, int id) +{ + struct i2c_client *client = priv->client; + int ret; + u8 chan; + u16 val; + + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FOLDBACK); + if (ret < 0) + return ret; + + /* No need to test if the chan is PoE4 as setting either bit for a + * 4P configured port disables the automatic configuration on both + * channels. + */ + chan = priv->port[id].chan[0]; + val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); + return i2c_smbus_write_byte_data(client, TPS23881_REG_FOLDBACK, val); +} + +static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + u8 chan; + u16 val; + int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; + + chan = priv->port[id].chan[0]; + val = tps23881_set_val(0, chan, 0, BIT(chan % 4), BIT(chan % 4)); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_set_val(val, chan, 0, BIT(chan % 4), + BIT(chan % 4)); + } + + ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); + if (ret) + return ret; + + /* Enable DC disconnect*/ + chan = priv->port[id].chan[0]; + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); + if (ret < 0) + return ret; + + val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); + if (ret) + return ret; + + return 0; +} + +static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + u8 chan; + u16 val; + int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; + + chan = priv->port[id].chan[0]; + val = tps23881_set_val(0, chan, 4, BIT(chan % 4), BIT(chan % 4)); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_set_val(val, chan, 4, BIT(chan % 4), + BIT(chan % 4)); + } + + ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); + if (ret) + return ret; + + /* PWOFF command resets lots of register which need to be + * configured again. According to the datasheet "It may take upwards + * of 5ms after PWOFFn command for all register values to be updated" + */ + mdelay(5); + + /* Disable DC disconnect*/ + chan = priv->port[id].chan[0]; + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); + if (ret < 0) + return ret; + + val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4)); + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); + if (ret) + return ret; + + /* Enable detection and classification */ + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN); + if (ret < 0) + return ret; + + chan = priv->port[id].chan[0]; + val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); + val = tps23881_set_val(val, chan, 4, BIT(chan % 4), BIT(chan % 4)); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), + BIT(chan % 4)); + val = tps23881_set_val(val, chan, 4, BIT(chan % 4), + BIT(chan % 4)); + } + + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); + if (ret) + return ret; + + /* No power policy */ + if (priv->port[id].pw_pol < 0) + return 0; + + ret = tps23881_pi_enable_manual_pol(priv, id); + if (ret < 0) + return ret; + + /* Set power policy */ + return tps23881_pi_set_pw_pol_limit(priv, id, priv->port[id].pw_pol, + priv->port[id].is_4p); +} + +static int +tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + bool enabled; + u8 chan; + u16 val; + int ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); + if (ret < 0) + return ret; + + chan = priv->port[id].chan[0]; + val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); + enabled = !!(val); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); + enabled &= !!(val); + } + + /* Return enabled status only if both channel are on this state */ + if (enabled) + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state = + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + + return 0; +} + +static int +tps23881_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, + struct pse_pw_status *pw_status) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + bool delivering; + u8 chan; + u16 val; + int ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); + if (ret < 0) + return ret; + + chan = priv->port[id].chan[0]; + val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); + delivering = !!(val); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); + delivering &= !!(val); + } + + /* Return delivering status only if both channel are on this state */ + if (delivering) + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + else + pw_status->c33_pw_status = + ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + + return 0; +} + +static int tps23881_pi_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + int ret; + u64 uV; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_INPUT_V); + if (ret < 0) + return ret; + + uV = ret & 0x3fff; + uV *= TPS23881_UV_STEP; + + return (int)uV; +} + +static int +tps23881_pi_get_chan_current(struct tps23881_priv *priv, u8 chan) +{ + struct i2c_client *client = priv->client; + int reg, ret; + u64 tmp_64; + + /* Registers 0x30 to 0x3d */ + reg = TPS23881_REG_CHAN1_A + (chan % 4) * 4 + (chan >= 4); + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + tmp_64 = ret & 0x3fff; + tmp_64 *= TPS23881_NA_STEP; + /* uA = nA / 1000 */ + tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000); + return (int)tmp_64; +} + +static int tps23881_pi_get_pw_class(struct pse_controller_dev *pcdev, + int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + int ret, reg; + u8 chan; + + chan = priv->port[id].chan[0]; + reg = TPS23881_REG_CHAN1_CLASS + (chan % 4); + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + return tps23881_calc_val(ret, chan, 4, 0x0f); +} + +static int +tps23881_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + int ret, uV, uA; + u64 tmp_64; + u8 chan; + + ret = tps23881_pi_get_voltage(&priv->pcdev, id); + if (ret < 0) + return ret; + uV = ret; + + chan = priv->port[id].chan[0]; + ret = tps23881_pi_get_chan_current(priv, chan); + if (ret < 0) + return ret; + uA = ret; + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + ret = tps23881_pi_get_chan_current(priv, chan); + if (ret < 0) + return ret; + uA += ret; + } + + tmp_64 = uV; + tmp_64 *= uA; + /* mW = uV * uA / 1000000000 */ + return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); +} + +static int +tps23881_pi_get_pw_limit_chan(struct tps23881_priv *priv, u8 chan) +{ + struct i2c_client *client = priv->client; + int ret, reg; + u16 val; + + reg = TPS23881_REG_2PAIR_POL1 + (chan % 4); + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, chan, 0, 0xff); + return val * TPS23881_MW_STEP; +} + +static int tps23881_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + int ret, mW; + u8 chan; + + chan = priv->port[id].chan[0]; + ret = tps23881_pi_get_pw_limit_chan(priv, chan); + if (ret < 0) + return ret; + + mW = ret; + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + ret = tps23881_pi_get_pw_limit_chan(priv, chan); + if (ret < 0) + return ret; + mW += ret; + } + + return mW; +} + +static int tps23881_pi_set_pw_limit(struct pse_controller_dev *pcdev, + int id, int max_mW) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + u8 pw_pol; + int ret; + + if (max_mW < TPS23881_MIN_PI_PW_LIMIT_MW || MAX_PI_PW < max_mW) { + dev_err(&priv->client->dev, + "power limit %d out of ranges [%d,%d]", + max_mW, TPS23881_MIN_PI_PW_LIMIT_MW, MAX_PI_PW); + return -ERANGE; + } + + ret = tps23881_pi_enable_manual_pol(priv, id); + if (ret < 0) + return ret; + + pw_pol = DIV_ROUND_CLOSEST_ULL(max_mW, TPS23881_MW_STEP); + + /* Save power policy to reconfigure it after a disabled call */ + priv->port[id].pw_pol = pw_pol; + return tps23881_pi_set_pw_pol_limit(priv, id, pw_pol, + priv->port[id].is_4p); +} + +static int +tps23881_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *pw_limit_ranges) +{ + struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; + + c33_pw_limit_ranges = kzalloc_obj(*c33_pw_limit_ranges); + if (!c33_pw_limit_ranges) + return -ENOMEM; + + c33_pw_limit_ranges->min = TPS23881_MIN_PI_PW_LIMIT_MW; + c33_pw_limit_ranges->max = MAX_PI_PW; + pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges; + + /* Return the number of ranges */ + return 1; +} + +/* Parse managers subnode into a array of device node */ +static int +tps23881_get_of_channels(struct tps23881_priv *priv, + struct device_node *chan_node[TPS23881_MAX_CHANS]) +{ + struct device_node *channels_node, *node; + int i, ret; + + if (!priv->np) + return -EINVAL; + + channels_node = of_find_node_by_name(priv->np, "channels"); + if (!channels_node) + return -EINVAL; + + for_each_child_of_node(channels_node, node) { + u32 chan_id; + + if (!of_node_name_eq(node, "channel")) + continue; + + ret = of_property_read_u32(node, "reg", &chan_id); + if (ret) { + ret = -EINVAL; + goto out; + } + + if (chan_id >= TPS23881_MAX_CHANS || chan_node[chan_id]) { + dev_err(&priv->client->dev, + "wrong number of port (%d)\n", chan_id); + ret = -EINVAL; + goto out; + } + + of_node_get(node); + chan_node[chan_id] = node; + } + + of_node_put(channels_node); + return 0; + +out: + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + of_node_put(chan_node[i]); + chan_node[i] = NULL; + } + + of_node_put(node); + of_node_put(channels_node); + return ret; +} + +struct tps23881_port_matrix { + u8 pi_id; + u8 lgcl_chan[2]; + u8 hw_chan[2]; + bool is_4p; + bool exist; +}; + +static int +tps23881_match_channel(const struct pse_pi_pairset *pairset, + struct device_node *chan_node[TPS23881_MAX_CHANS]) +{ + int i; + + /* Look on every channels */ + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (pairset->np == chan_node[i]) + return i; + } + + return -ENODEV; +} + +static bool +tps23881_is_chan_free(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], + int chan) +{ + int i; + + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (port_matrix[i].exist && + (port_matrix[i].hw_chan[0] == chan || + port_matrix[i].hw_chan[1] == chan)) + return false; + } + + return true; +} + +/* Fill port matrix with the matching channels */ +static int +tps23881_match_port_matrix(struct pse_pi *pi, int pi_id, + struct device_node *chan_node[TPS23881_MAX_CHANS], + struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) +{ + int ret; + + if (!pi->pairset[0].np) + return 0; + + ret = tps23881_match_channel(&pi->pairset[0], chan_node); + if (ret < 0) + return ret; + + if (!tps23881_is_chan_free(port_matrix, ret)) { + pr_err("tps23881: channel %d already used\n", ret); + return -ENODEV; + } + + port_matrix[pi_id].hw_chan[0] = ret; + port_matrix[pi_id].exist = true; + + if (!pi->pairset[1].np) + return 0; + + ret = tps23881_match_channel(&pi->pairset[1], chan_node); + if (ret < 0) + return ret; + + if (!tps23881_is_chan_free(port_matrix, ret)) { + pr_err("tps23881: channel %d already used\n", ret); + return -ENODEV; + } + + if (port_matrix[pi_id].hw_chan[0] / 4 != ret / 4) { + pr_err("tps23881: 4-pair PSE can only be set within the same 4 ports group"); + return -ENODEV; + } + + port_matrix[pi_id].hw_chan[1] = ret; + port_matrix[pi_id].is_4p = true; + + return 0; +} + +static int +tps23881_get_unused_chan(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], + int port_cnt) +{ + bool used; + int i, j; + + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + used = false; + + for (j = 0; j < port_cnt; j++) { + if (port_matrix[j].hw_chan[0] == i) { + used = true; + break; + } + + if (port_matrix[j].is_4p && + port_matrix[j].hw_chan[1] == i) { + used = true; + break; + } + } + + if (!used) + return i; + } + + return -ENODEV; +} + +/* Sort the port matrix to following particular hardware ports matrix + * specification of the tps23881. The device has two 4-ports groups and + * each 4-pair powered device has to be configured to use two consecutive + * logical channel in each 4 ports group (1 and 2 or 3 and 4). Also the + * hardware matrix has to be fully configured even with unused chan to be + * valid. + */ +static int +tps23881_sort_port_matrix(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) +{ + struct tps23881_port_matrix tmp_port_matrix[TPS23881_MAX_CHANS] = {0}; + int i, ret, port_cnt = 0, cnt_4ch_grp1 = 0, cnt_4ch_grp2 = 4; + + /* Configure 4p port matrix */ + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + int *cnt; + + if (!port_matrix[i].exist || !port_matrix[i].is_4p) + continue; + + if (port_matrix[i].hw_chan[0] < 4) + cnt = &cnt_4ch_grp1; + else + cnt = &cnt_4ch_grp2; + + tmp_port_matrix[port_cnt].exist = true; + tmp_port_matrix[port_cnt].is_4p = true; + tmp_port_matrix[port_cnt].pi_id = i; + tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0]; + tmp_port_matrix[port_cnt].hw_chan[1] = port_matrix[i].hw_chan[1]; + + /* 4-pair ports have to be configured with consecutive + * logical channels 0 and 1, 2 and 3. + */ + tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++; + tmp_port_matrix[port_cnt].lgcl_chan[1] = (*cnt)++; + + port_cnt++; + } + + /* Configure 2p port matrix */ + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + int *cnt; + + if (!port_matrix[i].exist || port_matrix[i].is_4p) + continue; + + if (port_matrix[i].hw_chan[0] < 4) + cnt = &cnt_4ch_grp1; + else + cnt = &cnt_4ch_grp2; + + tmp_port_matrix[port_cnt].exist = true; + tmp_port_matrix[port_cnt].pi_id = i; + tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++; + tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0]; + + port_cnt++; + } + + /* Complete the rest of the first 4 port group matrix even if + * channels are unused + */ + while (cnt_4ch_grp1 < 4) { + ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt); + if (ret < 0) { + pr_err("tps23881: port matrix issue, no chan available\n"); + return ret; + } + + if (port_cnt >= TPS23881_MAX_CHANS) { + pr_err("tps23881: wrong number of channels\n"); + return -ENODEV; + } + tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp1; + tmp_port_matrix[port_cnt].hw_chan[0] = ret; + cnt_4ch_grp1++; + port_cnt++; + } + + /* Complete the rest of the second 4 port group matrix even if + * channels are unused + */ + while (cnt_4ch_grp2 < 8) { + ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt); + if (ret < 0) { + pr_err("tps23881: port matrix issue, no chan available\n"); + return -ENODEV; + } + + if (port_cnt >= TPS23881_MAX_CHANS) { + pr_err("tps23881: wrong number of channels\n"); + return -ENODEV; + } + tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp2; + tmp_port_matrix[port_cnt].hw_chan[0] = ret; + cnt_4ch_grp2++; + port_cnt++; + } + + memcpy(port_matrix, tmp_port_matrix, sizeof(tmp_port_matrix)); + + return port_cnt; +} + +/* Write port matrix to the hardware port matrix and the software port + * matrix. + */ +static int +tps23881_write_port_matrix(struct tps23881_priv *priv, + struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], + int port_cnt) +{ + struct i2c_client *client = priv->client; + u8 pi_id, lgcl_chan, hw_chan; + u16 val = 0; + int i; + + for (i = 0; i < port_cnt; i++) { + pi_id = port_matrix[i].pi_id; + lgcl_chan = port_matrix[i].lgcl_chan[0]; + hw_chan = port_matrix[i].hw_chan[0] % 4; + + /* Set software port matrix for existing ports */ + if (port_matrix[i].exist) { + priv->port[pi_id].chan[0] = lgcl_chan; + priv->port[pi_id].exist = true; + } + + /* Initialize power policy internal value */ + priv->port[pi_id].pw_pol = -1; + + /* Set hardware port matrix for all ports */ + val |= hw_chan << (lgcl_chan * 2); + + if (!port_matrix[i].is_4p) + continue; + + lgcl_chan = port_matrix[i].lgcl_chan[1]; + hw_chan = port_matrix[i].hw_chan[1] % 4; + + /* Set software port matrix for existing ports */ + if (port_matrix[i].exist) { + priv->port[pi_id].is_4p = true; + priv->port[pi_id].chan[1] = lgcl_chan; + } + + /* Set hardware port matrix for all ports */ + val |= hw_chan << (lgcl_chan * 2); + } + + /* Write hardware ports matrix */ + return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val); +} + +static int +tps23881_set_ports_conf(struct tps23881_priv *priv, + struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) +{ + struct i2c_client *client = priv->client; + int i, ret; + u16 val; + + /* Set operating mode */ + ret = i2c_smbus_write_word_data(client, TPS23881_REG_OP_MODE, + TPS23881_OP_MODE_SEMIAUTO); + if (ret) + return ret; + + /* Disable DC disconnect */ + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DIS_EN, 0x0); + if (ret) + return ret; + + /* Set port power allocation */ + val = 0; + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!port_matrix[i].exist) + continue; + + if (port_matrix[i].is_4p) + val |= 0xf << ((port_matrix[i].lgcl_chan[0] / 2) * 4); + else + val |= 0x3 << ((port_matrix[i].lgcl_chan[0] / 2) * 4); + } + ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_POWER, val); + if (ret) + return ret; + + /* Enable detection and classification */ + val = 0; + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!port_matrix[i].exist) + continue; + + val |= BIT(port_matrix[i].lgcl_chan[0]) | + BIT(port_matrix[i].lgcl_chan[0] + 4); + if (port_matrix[i].is_4p) + val |= BIT(port_matrix[i].lgcl_chan[1]) | + BIT(port_matrix[i].lgcl_chan[1] + 4); + } + return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); +} + +static int +tps23881_set_ports_matrix(struct tps23881_priv *priv, + struct device_node *chan_node[TPS23881_MAX_CHANS]) +{ + struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS] = {0}; + int i, ret; + + /* Update with values for every PSE PIs */ + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + ret = tps23881_match_port_matrix(&priv->pcdev.pi[i], i, + chan_node, port_matrix); + if (ret) + return ret; + } + + ret = tps23881_sort_port_matrix(port_matrix); + if (ret < 0) + return ret; + + ret = tps23881_write_port_matrix(priv, port_matrix, ret); + if (ret) + return ret; + + return tps23881_set_ports_conf(priv, port_matrix); +} + +static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev) +{ + struct device_node *chan_node[TPS23881_MAX_CHANS] = {NULL}; + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + int ret, i; + + ret = tps23881_get_of_channels(priv, chan_node); + if (ret < 0) { + dev_warn(&priv->client->dev, + "Unable to parse port-matrix, default matrix will be used\n"); + return 0; + } + + ret = tps23881_set_ports_matrix(priv, chan_node); + + for (i = 0; i < TPS23881_MAX_CHANS; i++) + of_node_put(chan_node[i]); + + return ret; +} + +static int tps23881_power_class_table[] = { + -ERANGE, + 4000, + 7000, + 15500, + 30000, + 15500, + 15500, + -ERANGE, + 45000, + 60000, + 75000, + 90000, + 15500, + 45000, + -ERANGE, + -ERANGE, +}; + +static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + u8 reg, chan; + int ret; + u16 val; + + /* For a 4-pair the classification need 5ms to be completed */ + if (priv->port[id].is_4p) + mdelay(5); + + chan = priv->port[id].chan[0]; + reg = TPS23881_REG_DISC + (chan % 4); + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, chan, 4, 0xf); + return tps23881_power_class_table[val]; +} + +static const struct pse_controller_ops tps23881_ops = { + .setup_pi_matrix = tps23881_setup_pi_matrix, + .pi_enable = tps23881_pi_enable, + .pi_disable = tps23881_pi_disable, + .pi_get_admin_state = tps23881_pi_get_admin_state, + .pi_get_pw_status = tps23881_pi_get_pw_status, + .pi_get_pw_class = tps23881_pi_get_pw_class, + .pi_get_actual_pw = tps23881_pi_get_actual_pw, + .pi_get_voltage = tps23881_pi_get_voltage, + .pi_get_pw_limit = tps23881_pi_get_pw_limit, + .pi_set_pw_limit = tps23881_pi_set_pw_limit, + .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges, + .pi_get_pw_req = tps23881_pi_get_pw_req, +}; + +struct tps23881_info { + u8 dev_id; /* device ID and silicon revision */ + const char *fw_parity_name; /* parity code firmware file name */ + const char *fw_sram_name; /* SRAM code firmware file name */ +}; + +enum tps23881_model { + TPS23881, + TPS23881B, +}; + +static const struct tps23881_info tps23881_info[] = { + [TPS23881] = { + .dev_id = 0x22, + .fw_parity_name = "ti/tps23881/tps23881-parity-14.bin", + .fw_sram_name = "ti/tps23881/tps23881-sram-14.bin", + }, + [TPS23881B] = { + .dev_id = 0x24, + /* skip SRAM load, ROM provides Clause 145 hardware-level support */ + }, +}; + +struct tps23881_fw_conf { + u8 reg; + u8 val; +}; + +static const struct tps23881_fw_conf tps23881_fw_parity_conf[] = { + {.reg = 0x60, .val = 0x01}, + {.reg = 0x62, .val = 0x00}, + {.reg = 0x63, .val = 0x80}, + {.reg = 0x60, .val = 0xC4}, + {.reg = 0x1D, .val = 0xBC}, + {.reg = 0xD7, .val = 0x02}, + {.reg = 0x91, .val = 0x00}, + {.reg = 0x90, .val = 0x00}, + {.reg = 0xD7, .val = 0x00}, + {.reg = 0x1D, .val = 0x00}, + { /* sentinel */ } +}; + +static const struct tps23881_fw_conf tps23881_fw_sram_conf[] = { + {.reg = 0x60, .val = 0xC5}, + {.reg = 0x62, .val = 0x00}, + {.reg = 0x63, .val = 0x80}, + {.reg = 0x60, .val = 0xC0}, + {.reg = 0x1D, .val = 0xBC}, + {.reg = 0xD7, .val = 0x02}, + {.reg = 0x91, .val = 0x00}, + {.reg = 0x90, .val = 0x00}, + {.reg = 0xD7, .val = 0x00}, + {.reg = 0x1D, .val = 0x00}, + { /* sentinel */ } +}; + +static int tps23881_flash_sram_fw_part(struct i2c_client *client, + const char *fw_name, + const struct tps23881_fw_conf *fw_conf) +{ + const struct firmware *fw = NULL; + int i, ret; + + ret = request_firmware(&fw, fw_name, &client->dev); + if (ret) + return ret; + + dev_dbg(&client->dev, "Flashing %s\n", fw_name); + + /* Prepare device for RAM download */ + while (fw_conf->reg) { + ret = i2c_smbus_write_byte_data(client, fw_conf->reg, + fw_conf->val); + if (ret) + goto out; + + fw_conf++; + } + + /* Flash the firmware file */ + for (i = 0; i < fw->size; i++) { + ret = i2c_smbus_write_byte_data(client, + TPS23881_REG_SRAM_DATA, + fw->data[i]); + if (ret) + goto out; + } + +out: + release_firmware(fw); + return ret; +} + +static int tps23881_flash_sram_fw(struct i2c_client *client, + const struct tps23881_info *info) +{ + int ret; + + ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name, + tps23881_fw_parity_conf); + if (ret) + return ret; + + ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name, + tps23881_fw_sram_conf); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, TPS23881_REG_SRAM_CTRL, 0x18); + if (ret) + return ret; + + mdelay(12); + + return 0; +} + +/* Convert interrupt events to 0xff to be aligned with the chan + * number. + */ +static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset) +{ + u8 val; + + val = (reg_val >> (4 + field_offset) & 0xf0) | + (reg_val >> field_offset & 0x0f); + + return val; +} + +/* Convert chan number to port number */ +static void tps23881_set_notifs_helper(struct tps23881_priv *priv, + u8 chans, + unsigned long *notifs, + unsigned long *notifs_mask, + enum ethtool_pse_event event) +{ + u8 chan; + int i; + + if (!chans) + return; + + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!priv->port[i].exist) + continue; + /* No need to look at the 2nd channel in case of PoE4 as + * both registers are set. + */ + chan = priv->port[i].chan[0]; + + if (BIT(chan) & chans) { + *notifs_mask |= BIT(i); + notifs[i] |= event; + } + } +} + +static void tps23881_irq_event_over_temp(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int i; + + if (reg_val & TPS23881_REG_TSD) { + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!priv->port[i].exist) + continue; + + *notifs_mask |= BIT(i); + notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP; + } + } +} + +static int tps23881_irq_event_over_current(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int i, ret; + u8 chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 0); + if (!chans) + return 0; + + tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, + ETHTOOL_PSE_EVENT_OVER_CURRENT | + ETHTOOL_C33_PSE_EVENT_DISCONNECTION); + + /* Over Current event resets the power limit registers so we need + * to configured it again. + */ + for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) { + if (priv->port[i].pw_pol < 0) + continue; + + ret = tps23881_pi_enable_manual_pol(priv, i); + if (ret < 0) + return ret; + + /* Set power policy */ + ret = tps23881_pi_set_pw_pol_limit(priv, i, + priv->port[i].pw_pol, + priv->port[i].is_4p); + if (ret < 0) + return ret; + } + + return 0; +} + +static void tps23881_irq_event_disconnection(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + u8 chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 4); + if (chans) + tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, + ETHTOOL_C33_PSE_EVENT_DISCONNECTION); +} + +static int tps23881_irq_event_detection(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + enum ethtool_pse_event event; + int reg, ret, i, val; + unsigned long chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 0); + for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { + reg = TPS23881_REG_DISC + (i % 4); + ret = i2c_smbus_read_word_data(priv->client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, i, 0, 0xf); + /* If detection valid */ + if (val == 0x4) + event = ETHTOOL_C33_PSE_EVENT_DETECTION; + else + event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION; + + tps23881_set_notifs_helper(priv, BIT(i), notifs, + notifs_mask, event); + } + + return 0; +} + +static int tps23881_irq_event_classification(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int reg, ret, val, i; + unsigned long chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 4); + for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { + reg = TPS23881_REG_DISC + (i % 4); + ret = i2c_smbus_read_word_data(priv->client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, i, 4, 0xf); + /* Do not report classification event for unknown class */ + if (!val || val == 0x8 || val == 0xf) + continue; + + tps23881_set_notifs_helper(priv, BIT(i), notifs, + notifs_mask, + ETHTOOL_C33_PSE_EVENT_CLASSIFICATION); + } + + return 0; +} + +static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + struct i2c_client *client = priv->client; + int ret, val; + + /* The Supply event bit is repeated twice so we only need to read + * the one from the first byte. + */ + if (reg & TPS23881_REG_IT_SUPF) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT); + if (ret < 0) + return ret; + tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask); + } + + if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 | + TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT); + if (ret < 0) + return ret; + ret = tps23881_irq_event_over_current(priv, ret, notifs, + notifs_mask); + if (ret) + return ret; + + tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask); + } + + if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 | + TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT); + if (ret < 0) + return ret; + + val = ret; + ret = tps23881_irq_event_detection(priv, val, notifs, + notifs_mask); + if (ret) + return ret; + + ret = tps23881_irq_event_classification(priv, val, notifs, + notifs_mask); + if (ret) + return ret; + } + return 0; +} + +static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + int ret, it_mask, retry; + + /* Get interruption mask */ + ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK); + if (ret < 0) + return ret; + it_mask = ret; + + /* Read interrupt register until it frees the interruption pin. */ + retry = 0; + while (true) { + if (retry > TPS23881_MAX_IRQ_RETRIES) { + dev_err(&client->dev, "interrupt never freed"); + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT); + if (ret < 0) + return ret; + + /* No more relevant interruption */ + if (!(ret & it_mask)) + return 0; + + ret = tps23881_irq_event_handler(priv, (u16)ret, notifs, + notifs_mask); + if (ret) + return ret; + + retry++; + } + return 0; +} + +static int tps23881_setup_irq(struct tps23881_priv *priv, int irq) +{ + struct i2c_client *client = priv->client; + struct pse_irq_desc irq_desc = { + .name = "tps23881-irq", + .map_event = tps23881_irq_handler, + }; + int ret; + u16 val; + + if (!irq) { + dev_err(&client->dev, "interrupt is missing"); + return -EINVAL; + } + + val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF | + TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC | + TPS23881_REG_IT_DISF; + val |= val << 8; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val); + if (ret) + return ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK); + if (ret < 0) + return ret; + + val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE; + val |= val << 8; + val |= (u16)ret; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val); + if (ret < 0) + return ret; + + /* Reset interrupts registers */ + ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET, + TPS23881_REG_CLRAIN); + if (ret < 0) + return ret; + + return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc); +} + +static int tps23881_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct tps23881_info *info; + struct tps23881_priv *priv; + struct gpio_desc *reset; + int ret; + u8 val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "i2c check functionality failed\n"); + return -ENXIO; + } + + info = i2c_get_match_data(client); + if (!info) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset)) + return dev_err_probe(&client->dev, PTR_ERR(reset), "Failed to get reset GPIO\n"); + + if (reset) { + /* TPS23880 datasheet (Rev G) indicates minimum reset pulse is 5us */ + usleep_range(5, 10); + gpiod_set_value_cansleep(reset, 0); /* De-assert reset */ + + /* TPS23880 datasheet indicates the minimum time after power on reset + * should be 20ms, but the document describing how to load SRAM ("How + * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E)) + * indicates we should delay that programming by at least 50ms. So + * we'll wait the entire 50ms here to ensure we're safe to go to the + * SRAM loading procedure. + */ + msleep(50); + } + + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_DEVID); + if (ret < 0) + return ret; + + if (ret != info->dev_id) { + dev_err(dev, "Wrong device ID\n"); + return -ENXIO; + } + + if (info->fw_sram_name) { + ret = tps23881_flash_sram_fw(client, info); + if (ret < 0) + return ret; + } + + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV); + if (ret < 0) + return ret; + + if (ret == 0xFF) { + dev_err(&client->dev, "Device entered safe mode\n"); + return -ENXIO; + } + dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret, + ret == 0x00 ? " (ROM firmware)" : ""); + + /* Set configuration B, 16 bit access on a single device address */ + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK); + if (ret < 0) + return ret; + + val = ret | TPS23881_REG_NBITACC; + ret = i2c_smbus_write_byte_data(client, TPS23881_REG_GEN_MASK, val); + if (ret) + return ret; + + priv->client = client; + i2c_set_clientdata(client, priv); + priv->np = dev->of_node; + + priv->pcdev.owner = THIS_MODULE; + priv->pcdev.ops = &tps23881_ops; + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_C33; + priv->pcdev.nr_lines = TPS23881_MAX_CHANS; + priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC; + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) { + return dev_err_probe(dev, ret, + "failed to register PSE controller\n"); + } + + return tps23881_setup_irq(priv, client->irq); +} + +static const struct i2c_device_id tps23881_id[] = { + { .name = "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] }, + { .name = "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps23881_id); + +static const struct of_device_id tps23881_of_match[] = { + { + .compatible = "ti,tps23881", + .data = &tps23881_info[TPS23881] + }, + { + .compatible = "ti,tps23881b", + .data = &tps23881_info[TPS23881B] + }, + { }, +}; +MODULE_DEVICE_TABLE(of, tps23881_of_match); + +static struct i2c_driver tps23881_driver = { + .probe = tps23881_i2c_probe, + .id_table = tps23881_id, + .driver = { + .name = "tps23881", + .of_match_table = tps23881_of_match, + }, +}; +module_i2c_driver(tps23881_driver); + +MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>"); +MODULE_DESCRIPTION("TI TPS23881 PoE PSE Controller driver"); +MODULE_LICENSE("GPL"); |
