Re: [PATCH net-next v2 8/8] net: pse-pd: Add PD692x0 PSE controller driver

From: Oleksij Rempel
Date: Mon Dec 04 2023 - 18:00:35 EST


On Fri, Dec 01, 2023 at 06:10:30PM +0100, Kory Maincent wrote:
> Add a new driver for the PD692x0 I2C Power Sourcing Equipment controller.
> This driver only support i2c communication for now.
>
> Sponsored-by: Dent Project <dentproject@xxxxxxxxxxxxxxxxxxx>
> Signed-off-by: Kory Maincent <kory.maincent@xxxxxxxxxxx>
> ---
>
> This driver is based on the patch merged in an immutable branch from Jakub
> repo. It is Tagged at:
> git://git.kernel.org/pub/scm/linux/kernel/git/kuba/linux.git firmware_loader-add-upload-error
>
> Change in v2:
> - Drop of_match_ptr
> - Follow the "c33" PoE prefix naming change.
> - Remove unused delay_recv variable. Then, remove struct pd692x0_msg_content
> which is similar to struct pd692x0_msg.
> - Fix a weird sleep loop.
> - Improve pd692x0_recv_msg for better readability.
> - Fix a warning reported by Simon on a pd692x0_fw_write_line call.
> ---
> MAINTAINERS | 1 +
> drivers/net/pse-pd/Kconfig | 11 +
> drivers/net/pse-pd/Makefile | 1 +
> drivers/net/pse-pd/pd692x0.c | 1025 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 1038 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b746684f3fd3..3cbafca0cdf4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14240,6 +14240,7 @@ M: Kory Maincent <kory.maincent@xxxxxxxxxxx>
> L: netdev@xxxxxxxxxxxxxxx
> S: Maintained
> F: Documentation/devicetree/bindings/net/pse-pd/microchip,pd692x0.yaml
> +F: drivers/net/pse-pd/pd692x0.c
>
> MICROCHIP POLARFIRE FPGA DRIVERS
> M: Conor Dooley <conor.dooley@xxxxxxxxxxxxx>
> diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
> index 687dec49c1e1..e3a6ba669f20 100644
> --- a/drivers/net/pse-pd/Kconfig
> +++ b/drivers/net/pse-pd/Kconfig
> @@ -20,4 +20,15 @@ config PSE_REGULATOR
> 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_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.
> +
> endif
> diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
> index 1b8aa4c70f0b..9c12c4a65730 100644
> --- a/drivers/net/pse-pd/Makefile
> +++ b/drivers/net/pse-pd/Makefile
> @@ -4,3 +4,4 @@
> obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
>
> obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
> +obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
> diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c
> new file mode 100644
> index 000000000000..6d921dfcfb07
> --- /dev/null
> +++ b/drivers/net/pse-pd/pd692x0.c
> @@ -0,0 +1,1025 @@
> +// 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@xxxxxxxxxxx>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pse-pd/pse.h>
> +#include <linux/i2c.h>
> +#include <linux/delay.h>
> +#include <linux/firmware.h>
> +
> +#define PD692X0_PSE_NAME "pd692x0_pse"
> +
> +#define PD692X0_MAX_LOGICAL_PORTS 48
> +#define PD692X0_MAX_HW_PORTS 96
> +
> +#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
> +
> +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_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,
> +
> + /* add new message above here */
> + PD692X0_MSG_CNT
> +};
> +
> +struct pd692x0_priv {
> + struct i2c_client *client;
> + struct pse_controller_dev pcdev;
> +
> + 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;
> +
> + enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_LOGICAL_PORTS];
> +};
> +
> +/* 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_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 = { 0, 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},
> + },
> +};
> +
> +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;

This overwrites initial error message returned by the i2c_master_send().
return ret < 0 ? ret : -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");

May be better to have here a bit more verbose error message. For example
print values which we actually got?

> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int pd692x0_try_recv_msg(const struct i2c_client *client,
> + struct pd692x0_msg *msg,
> + struct pd692x0_msg *buf)
> +{
> + msleep(30);

It would be good to have some comments on sleeps. For example is it
based on documentation or on testing. It is related to all seeps in this
driver.

> +
> + memset(buf, 0, sizeof(*buf));
> + i2c_master_recv(client, (u8 *)buf, sizeof(*buf));

i2c level errors are ignored.

> + if (buf->key)
> + return 1;
> +
> + msleep(100);
> +
> + memset(buf, 0, sizeof(*buf));
> + i2c_master_recv(client, (u8 *)buf, sizeof(*buf));

same here. i2c level errors are ignored.

> + if (buf->key)
> + return 1;
> +
> + return 0;
> +}
> +
> +/* 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_success;
> +
> + msleep(10000);
> +
> + ret = pd692x0_send_msg(priv, msg);
> + if (ret)
> + return ret;
> +
> + ret = pd692x0_try_recv_msg(client, msg, buf);
> + if (ret)
> + goto out_success;
> +
> + return pd692x0_reset(priv);
> +
> +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\n");
> + 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");

It will be better to export this messages to the user space with
NL_SET_ERR_MSG().

> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int pd692x0_ethtool_set_config(struct pse_controller_dev *pcdev,
> + unsigned long id,
> + struct netlink_ext_ack *extack,
> + const struct pse_control_config *config)
> +{
> + 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] == config->c33_admin_control)
> + return 0;
> +
> + msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
> + switch (config->c33_admin_control) {
> + case ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED:
> + msg.data[0] = 0x1;
> + break;
> + case ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED:
> + msg.data[0] = 0x0;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + msg.sub[2] = id;
> + ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
> + if (ret < 0)
> + return ret;
> +
> + priv->admin_state[id] = config->c33_admin_control;
> +
> + return 0;
> +}
> +
> +static int pd692x0_ethtool_get_status(struct pse_controller_dev *pcdev,
> + unsigned long id,
> + struct netlink_ext_ack *extack,
> + struct pse_control_status *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)
> + status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
> + else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
> + status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
> + else if (buf.sub[0] == 0x12)
> + status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
> + else
> + status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
> +
> + if (buf.sub[1])
> + status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
> + else
> + status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
> +
> + priv->admin_state[id] = status->c33_admin_state;
> +
> + return 0;
> +}
> +
> +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;
> +}
> +
> +static const struct pse_controller_ops pd692x0_ops = {
> + .ethtool_get_status = pd692x0_ethtool_get_status,
> + .ethtool_set_config = pd692x0_ethtool_set_config,
> +};
> +
> +struct matrix {
> + u8 hw_port_a;
> + u8 hw_port_b;
> +};
> +
> +static int
> +pd692x0_set_ports_matrix(struct pd692x0_priv *priv,
> + const struct matrix port_matrix[PD692X0_MAX_LOGICAL_PORTS])
> +{
> + 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_LOGICAL_PORTS; i++) {
> + msg.sub[2] = i;
> + msg.data[0] = port_matrix[i].hw_port_a;
> + msg.data[1] = port_matrix[i].hw_port_b;
> +
> + 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_get_of_matrix(struct device *dev,
> + struct matrix port_matrix[PD692X0_MAX_LOGICAL_PORTS])
> +{
> + u32 val[PD692X0_MAX_LOGICAL_PORTS * 3];
> + int ret, i, ports_matrix_size;
> +
> + ports_matrix_size = device_property_count_u32(dev, "ports-matrix");
> + if (ports_matrix_size <= 0)
> + return -EINVAL;
> + if (ports_matrix_size % 3 ||
> + ports_matrix_size > PD692X0_MAX_LOGICAL_PORTS * 3) {
> + dev_err(dev, "Not valid ports-matrix property size: %d\n",
> + ports_matrix_size);
> + return -EINVAL;
> + }
> +
> + ret = device_property_read_u32_array(dev, "ports-matrix", val,
> + ports_matrix_size);
> + if (ret < 0)
> + return ret;
> +
> + /* Init Matrix */
> + for (i = 0; i < PD692X0_MAX_LOGICAL_PORTS; i++) {
> + port_matrix[i].hw_port_a = 0xff;
> + port_matrix[i].hw_port_b = 0xff;
> + }
> +
> + /* Update with values from DT */
> + for (i = 0; i < ports_matrix_size; i += 3) {
> + unsigned int logical_port;
> +
> + if (val[i] >= PD692X0_MAX_LOGICAL_PORTS) {
> + dev_err(dev, "Not valid ports-matrix property\n");
> + return -EINVAL;
> + }
> + logical_port = val[i];
> +
> + if (val[i + 1] < PD692X0_MAX_HW_PORTS)
> + port_matrix[logical_port].hw_port_a = val[i + 1];
> +
> + if (val[i + 2] < PD692X0_MAX_HW_PORTS)
> + port_matrix[logical_port].hw_port_b = val[i + 2];
> + }
> +
> + return 0;
> +}
> +
> +static int pd692x0_update_matrix(struct pd692x0_priv *priv)
> +{
> + struct matrix port_matrix[PD692X0_MAX_LOGICAL_PORTS];
> + struct device *dev = &priv->client->dev;
> + int ret;
> +
> + ret = pd692x0_get_of_matrix(dev, port_matrix);
> + if (ret < 0) {
> + dev_warn(dev,
> + "Unable to parse port-matrix, saved matrix will be used\n");
> + return 0;
> + }
> +
> + ret = pd692x0_set_ports_matrix(priv, port_matrix);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +#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, (size_t)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 0;
> +}
> +
> +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)) {

I assume, testing ret is still better error handling.

> + return FW_UPLOAD_ERR_NONE;
> + } else {
> + dev_err(&client->dev,
> + "Wrong FW download process answer (%*pE)\n",
> + msg_size, fw_msg_buf);

Here we can have two different error types, i2c error store in ret and
wrong response.

> + return FW_UPLOAD_ERR_HW_ERROR;
> + }
> +}
> +
> +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) {
> + 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_update_matrix(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)
> +{
> + struct device *dev = &client->dev;
> + struct pd692x0_msg buf = {0};
> + struct pd692x0_msg_ver ver;
> + struct pd692x0_priv *priv;
> + struct fw_upload *fwl;
> + int 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;
> +
> + priv->client = client;
> + i2c_set_clientdata(client, priv);
> +
> + priv->pcdev.owner = THIS_MODULE;
> + priv->pcdev.ops = &pd692x0_ops;
> + priv->pcdev.dev = dev;
> + priv->pcdev.types = PSE_C33;
> + priv->pcdev.of_pse_n_cells = 1;
> + priv->pcdev.nr_lines = PD692X0_MAX_LOGICAL_PORTS;
> + 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)) {
> + dev_err(dev, "Failed to register to the Firmware Upload API\n");
> + ret = PTR_ERR(fwl);
> + return ret;
> + }
> + priv->fwl = fwl;
> +
> + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
> + if (ret != sizeof(buf)) {
> + dev_err(dev, "Failed to get device status\n");
> + ret = -EIO;

Overwritten error value.

> + goto err_fw_unregister;
> + }
> +
> + if (buf.key != 0x03 || buf.echo != 0xff || buf.sub[0] & 0x01) {
> + dev_err(dev, "PSE controller error\n");

There is same pattern detection on other place. It will be to move it to
a separate function and add some comments.

> + ret = -EIO;
> + goto err_fw_unregister;
> + }
> +
> + if (buf.sub[0] & 0x02) {

Is it possible to replace most of this magic numbers?

> + dev_err(dev, "PSE firmware error. Please update it.\n");

I assume, the message is saying that firmware image is corrupt and
should be overwritten? "update" feels more like, there is old firmware
version and should be replaced with a new one :)

> + priv->fw_state = PD692X0_FW_BROKEN;
> + return 0;
> + }
> +
> + 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;
> + return 0;
> + }
> +
> + ret = pd692x0_update_matrix(priv);
> + if (ret < 0) {
> + dev_err(dev, "Error configuring ports matrix (%pe)\n",
> + ERR_PTR(ret));
> + goto err_fw_unregister;
> + }
> +
> + priv->fw_state = PD692X0_FW_OK;
> + return 0;
> +
> +err_fw_unregister:
> + firmware_upload_unregister(priv->fwl);
> + return ret;
> +}
> +
> +static void pd692x0_i2c_remove(struct i2c_client *client)
> +{
> + struct pd692x0_priv *priv = i2c_get_clientdata(client);
> +
> + firmware_upload_unregister(priv->fwl);
> +}
> +
> +static const struct i2c_device_id pd692x0_id[] = {
> + { PD692X0_PSE_NAME, 0 },
> + { },
> +};
> +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@xxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Microchip PD692x0 PoE PSE Controller driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.25.1
>
>
>

--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |