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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 |




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux