Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Kory Maincent 5736 100.00% 1 100.00%
Total 5736 1


// 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/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pse-pd/pse.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

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,

	/* add new message above here */
	PD692X0_MSG_CNT
};

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;

	enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
};

/* 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 = {   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;

	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;
}

static int pd692x0_pi_is_enabled(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;

	if (buf.sub[1]) {
		priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
		return 1;
	} else {
		priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
		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;
}

struct pd692x0_manager {
	struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
	int nports;
};

struct pd692x0_matrix {
	u8 hw_port_a;
	u8 hw_port_b;
};

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[PD692X0_MAX_MANAGERS])
{
	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;

		nmanagers++;
	}

	of_node_put(managers_node);
	return nmanagers;

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(node);
	of_node_put(managers_node);
	return ret;
}

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,
			 int nmanagers,
			 struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
{
	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, 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, 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,
			   const struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
{
	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_setup_pi_matrix(struct pse_controller_dev *pcdev)
{
	struct pd692x0_manager manager[PD692X0_MAX_MANAGERS] = {0};
	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
	struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
	int ret, i, j, nmanagers;

	/* Should we flash the port matrix */
	if (priv->fw_state != PD692X0_FW_OK &&
	    priv->fw_state != PD692X0_FW_COMPLETE)
		return 0;

	ret = pd692x0_of_get_managers(priv, manager);
	if (ret < 0)
		return ret;

	nmanagers = ret;
	ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
	if (ret)
		goto out;

	ret = pd692x0_write_ports_matrix(priv, port_matrix);
	if (ret)
		goto out;

out:
	for (i = 0; i < nmanagers; i++) {
		for (j = 0; j < manager[i].nports; j++)
			of_node_put(manager[i].port_node[j]);
	}
	return ret;
}

static const struct pse_controller_ops pd692x0_ops = {
	.setup_pi_matrix = pd692x0_setup_pi_matrix,
	.ethtool_get_status = pd692x0_ethtool_get_status,
	.pi_enable = pd692x0_pi_enable,
	.pi_disable = pd692x0_pi_disable,
	.pi_is_enabled = pd692x0_pi_is_enabled,
};

#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_setup_pi_matrix(&priv->pcdev);
	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 pd692x0_msg msg, buf = {0}, zero = {0};
	struct device *dev = &client->dev;
	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);

	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;
		}
	}

	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;
	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);

	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@bootlin.com>");
MODULE_DESCRIPTION("Microchip PD692x0 PoE PSE Controller driver");
MODULE_LICENSE("GPL");