Re: [PATCH v9 2/7] drivers: mfd: Add a driver for IEI WT61P803 PUZZLE MCU

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

 



Greg,

Would you be kind enough to take a look at the SYS imp. please?

I've marked the start with:

###########################################
###########################################


On Tue, 24 Aug 2021, Luka Kovacic wrote:
> Add a driver for the IEI WT61P803 PUZZLE microcontroller, used in some
> IEI Puzzle series devices. The microcontroller controls system power,
> temperature sensors, fans and LEDs.
> 
> This driver implements the core functionality for device communication
> over the system serial (serdev bus). It handles MCU messages and the
> internal MCU properties. Some properties can be managed over sysfs.
> 
> Signed-off-by: Luka Kovacic <luka.kovacic@xxxxxxxxxx>
> Signed-off-by: Pavo Banicevic <pavo.banicevic@xxxxxxxxxx>
> Cc: Luka Perkov <luka.perkov@xxxxxxxxxx>
> Cc: Robert Marko <robert.marko@xxxxxxxxxx>
> ---
>  drivers/mfd/Kconfig                     |   8 +
>  drivers/mfd/Makefile                    |   1 +
>  drivers/mfd/iei-wt61p803-puzzle.c       | 908 ++++++++++++++++++++++++
>  include/linux/mfd/iei-wt61p803-puzzle.h |  66 ++
>  4 files changed, 983 insertions(+)
>  create mode 100644 drivers/mfd/iei-wt61p803-puzzle.c
>  create mode 100644 include/linux/mfd/iei-wt61p803-puzzle.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 6a3fd2d75f96..c2183eb0775f 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2170,6 +2170,14 @@ config SGI_MFD_IOC3
>  	  If you have an SGI Origin, Octane, or a PCI IOC3 card,
>  	  then say Y. Otherwise say N.
>  
> +config MFD_IEI_WT61P803_PUZZLE
> +	tristate "IEI WT61P803 PUZZLE MCU driver"
> +	depends on SERIAL_DEV_BUS
> +	help
> +	  IEI WT61P803 PUZZLE is a system power management microcontroller
> +	  used for fan control, temperature sensor reading, LED control
> +	  and system identification.
> +
>  config MFD_INTEL_M10_BMC
>  	tristate "Intel MAX 10 Board Management Controller"
>  	depends on SPI_MASTER
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 8116c19d5fd4..42b9767ec37a 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -236,6 +236,7 @@ obj-$(CONFIG_MFD_DLN2)		+= dln2.o
>  obj-$(CONFIG_MFD_RT4831)	+= rt4831.o
>  obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
>  obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
> +obj-$(CONFIG_MFD_IEI_WT61P803_PUZZLE)	+= iei-wt61p803-puzzle.o
>  
>  intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
> diff --git a/drivers/mfd/iei-wt61p803-puzzle.c b/drivers/mfd/iei-wt61p803-puzzle.c
> new file mode 100644
> index 000000000000..596ecbc65627
> --- /dev/null
> +++ b/drivers/mfd/iei-wt61p803-puzzle.c
> @@ -0,0 +1,908 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* IEI WT61P803 PUZZLE MCU Driver

This breaks coding standard conventions.

top line should be empty.

Place a '\n' below it too please.

> + * System management microcontroller for fan control, temperature sensor reading,
> + * LED control and system identification on IEI Puzzle series ARM-based appliances.
> + *
> + * Copyright (C) 2020 Sartura Ltd.
> + * Author: Luka Kovacic <luka.kovacic@xxxxxxxxxx>
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/delay.h>
> +#include <linux/export.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/iei-wt61p803-puzzle.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/property.h>
> +#include <linux/sched.h>
> +#include <linux/serdev.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +#include <asm/unaligned.h>

Are you 100% sure all of these are in use?

> +/* start, payload and XOR checksum at end */
> +#define IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH	(1 + 20 + 1)
> +#define IEI_WT61P803_PUZZLE_RESP_BUF_SIZE	512
> +
> +#define IEI_WT61P803_PUZZLE_MAC_LENGTH			17
> +#define IEI_WT61P803_PUZZLE_SN_LENGTH			36
> +#define IEI_WT61P803_PUZZLE_VERSION_LENGTH		 6
> +#define IEI_WT61P803_PUZZLE_BUILD_INFO_LENGTH		16
> +#define IEI_WT61P803_PUZZLE_PROTOCOL_VERSION_LENGTH	 8
> +#define IEI_WT61P803_PUZZLE_NB_MAC			 8
> +
> +/* Use HZ as a timeout value throughout the driver */
> +#define IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT HZ
> +
> +enum iei_wt61p803_puzzle_attribute_type {
> +	IEI_WT61P803_PUZZLE_VERSION,
> +	IEI_WT61P803_PUZZLE_BUILD_INFO,
> +	IEI_WT61P803_PUZZLE_BOOTLOADER_MODE,
> +	IEI_WT61P803_PUZZLE_PROTOCOL_VERSION,
> +	IEI_WT61P803_PUZZLE_SERIAL_NUMBER,
> +	IEI_WT61P803_PUZZLE_MAC_ADDRESS,
> +	IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS,
> +	IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY,
> +	IEI_WT61P803_PUZZLE_POWER_STATUS,
> +};
> +
> +struct iei_wt61p803_puzzle_device_attribute {
> +	struct device_attribute dev_attr;
> +	enum iei_wt61p803_puzzle_attribute_type type;
> +	u8 index;
> +};

Kernel-doc?

> +/**
> + * struct iei_wt61p803_puzzle_mcu_status - MCU flags state
> + * @ac_recovery_status_flag:	AC Recovery Status Flag
> + * @power_loss_recovery:	System recovery after power loss
> + * @power_status:		System Power-on Method
> + */
> +struct iei_wt61p803_puzzle_mcu_status {
> +	u8 ac_recovery_status_flag;
> +	u8 power_loss_recovery;
> +	u8 power_status;
> +};
> +
> +/**
> + * struct iei_wt61p803_puzzle_reply - MCU reply
> + * @size:	Size of the MCU reply
> + * @data:	Full MCU reply buffer
> + * @state:	Current state of the packet
> + * @received:	Was the response fullfilled
> + */
> +struct iei_wt61p803_puzzle_reply {
> +	size_t size;
> +	unsigned char data[IEI_WT61P803_PUZZLE_RESP_BUF_SIZE];
> +	struct completion received;
> +};
> +
> +/**
> + * struct iei_wt61p803_puzzle_mcu_version - MCU version status
> + * @version:		Primary firmware version
> + * @build_info:		Build date and time
> + * @bootloader_mode:	Status of the MCU operation
> + * @protocol_version:	MCU communication protocol version
> + * @serial_number:	Device factory serial number
> + * @mac_address:	Device factory MAC addresses
> + *
> + * Last element of arrays is reserved for '\0'.
> + */
> +struct iei_wt61p803_puzzle_mcu_version {
> +	char version[IEI_WT61P803_PUZZLE_VERSION_LENGTH + 1];
> +	char build_info[IEI_WT61P803_PUZZLE_BUILD_INFO_LENGTH + 1];
> +	bool bootloader_mode;
> +	char protocol_version[IEI_WT61P803_PUZZLE_PROTOCOL_VERSION_LENGTH + 1];
> +	char serial_number[IEI_WT61P803_PUZZLE_SN_LENGTH + 1];
> +	char mac_address[IEI_WT61P803_PUZZLE_NB_MAC][IEI_WT61P803_PUZZLE_MAC_LENGTH + 1];
> +};
> +
> +/**
> + * struct iei_wt61p803_puzzle - IEI WT61P803 PUZZLE MCU Driver
> + * @serdev:		Pointer to underlying serdev device
> + * @dev:		Pointer to underlying dev device
> + * @reply_lock:		Reply mutex lock
> + * @reply:		Pointer to the iei_wt61p803_puzzle_reply struct
> + * @version:		MCU version related data
> + * @status:		MCU status related data
> + * @response_buffer	Command response buffer allocation
> + * @lock		General member mutex lock
> + */
> +struct iei_wt61p803_puzzle {
> +	struct serdev_device *serdev;
> +	struct device *dev;
> +	struct mutex reply_lock; /* lock to prevent multiple firmware calls */
> +	struct iei_wt61p803_puzzle_reply *reply;
> +	struct iei_wt61p803_puzzle_mcu_version version;
> +	struct iei_wt61p803_puzzle_mcu_status status;
> +	unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE];
> +	struct mutex lock; /* lock to protect response buffer */
> +};
> +
> +static unsigned char iei_wt61p803_puzzle_checksum(unsigned char *buf, size_t len)
> +{
> +	unsigned char checksum = 0;
> +	size_t i;
> +
> +	for (i = 0; i < len; i++)
> +		checksum ^= buf[i];
> +	return checksum;
> +}

Are you sure there isn't a generic implementation you can use?

> +static int iei_wt61p803_puzzle_process_resp(struct iei_wt61p803_puzzle *mcu,
> +					    const unsigned char *raw_resp_data, size_t size)
> +{
> +	unsigned char checksum;
> +
> +	/* Check the incoming frame header */
> +	if (!(raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START ||
> +	      raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER ||
> +	     (raw_resp_data[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM &&
> +	      raw_resp_data[1] == IEI_WT61P803_PUZZLE_CMD_EEPROM_READ))) {
> +		if (mcu->reply->size + size >= sizeof(mcu->reply->data))
> +			return -EIO;
> +
> +		/* Append the frame to existing data */
> +		memcpy(mcu->reply->data + mcu->reply->size, raw_resp_data, size);
> +		mcu->reply->size += size;
> +	} else {
> +		if (size >= sizeof(mcu->reply->data))
> +			return -EIO;
> +
> +		/* Start processing a new frame */
> +		memcpy(mcu->reply->data, raw_resp_data, size);
> +		mcu->reply->size = size;
> +	}
> +
> +	checksum = iei_wt61p803_puzzle_checksum(mcu->reply->data, mcu->reply->size - 1);
> +	if (checksum != mcu->reply->data[mcu->reply->size - 1]) {
> +		/* The checksum isn't matched yet, wait for new frames */
> +		return size;
> +	}
> +
> +	/* Received all the data */
> +	complete(&mcu->reply->received);
> +
> +	return size;
> +}
> +
> +static int iei_wt61p803_puzzle_recv_buf(struct serdev_device *serdev,
> +					const unsigned char *data, size_t size)
> +{
> +	struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev);
> +	int ret;
> +
> +	ret = iei_wt61p803_puzzle_process_resp(mcu, data, size);
> +	/* Return the number of processed bytes if function returns error,
> +	 * discard the remaining incoming data, since the frame this data
> +	 * belongs to is broken anyway
> +	 */

Place this comment above the function call please.

And fix all of your comments to conform to the kernel Coding Standard.

> +	if (ret < 0)
> +		return size;
> +
> +	return ret;
> +}
> +
> +static const struct serdev_device_ops iei_wt61p803_puzzle_serdev_device_ops = {
> +	.receive_buf  = iei_wt61p803_puzzle_recv_buf,
> +	.write_wakeup = serdev_device_write_wakeup,
> +};
> +
> +/**
> + * iei_wt61p803_puzzle_write_command_watchdog() - Watchdog of the normal cmd
> + * @mcu: Pointer to the iei_wt61p803_puzzle core MFD struct
> + * @cmd: Pointer to the char array to send (size should be content + 1 (xor))
> + * @size: Size of the cmd char array
> + * @reply_data: Pointer to the reply/response data array (should be allocated)
> + * @reply_size: Pointer to size_t (size of reply_data)
> + * @retry_count: Number of times to retry sending the command to the MCU
> + */
> +int iei_wt61p803_puzzle_write_command_watchdog(struct iei_wt61p803_puzzle *mcu,
> +					       unsigned char *cmd, size_t size,
> +					       unsigned char *reply_data,
> +					       size_t *reply_size, int retry_count)
> +{
> +	struct device *dev = &mcu->serdev->dev;
> +	int ret, i;
> +
> +	for (i = 0; i < retry_count; i++) {
> +		ret = iei_wt61p803_puzzle_write_command(mcu, cmd, size,
> +							reply_data, reply_size);
> +		if (ret != -ETIMEDOUT)
> +			return ret;
> +	}
> +
> +	dev_err(dev, "Command response timed out. Retries: %d\n", retry_count);
> +
> +	return -ETIMEDOUT;
> +}
> +EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command_watchdog);

Don't rely on the export/prototype for forward declaration.

Place this function below iei_wt61p803_puzzle_write_command() please.

> +/**
> + * iei_wt61p803_puzzle_write_command() - Send a structured command to the MCU
> + * @mcu: Pointer to the iei_wt61p803_puzzle core MFD struct
> + * @cmd: Pointer to the char array to send (size should be content + 1 (xor))
> + * @size: Size of the cmd char array
> + * @reply_data: Pointer to the reply/response data array (should be allocated)
> + *
> + * Sends a structured command to the MCU.
> + */
> +int iei_wt61p803_puzzle_write_command(struct iei_wt61p803_puzzle *mcu,
> +				      unsigned char *cmd, size_t size,
> +				      unsigned char *reply_data,
> +				      size_t *reply_size)
> +{
> +	struct device *dev = &mcu->serdev->dev;
> +	int ret;
> +
> +	if (size <= 1 || size > IEI_WT61P803_PUZZLE_MAX_COMMAND_LENGTH)
> +		return -EINVAL;
> +
> +	mutex_lock(&mcu->reply_lock);
> +
> +	cmd[size - 1] = iei_wt61p803_puzzle_checksum(cmd, size - 1);
> +
> +	/* Initialize reply struct */
> +	reinit_completion(&mcu->reply->received);
> +	mcu->reply->size = 0;

'\n' here, as the next line is not related to the comment.

> +	usleep_range(2000, 10000);

What's the purpose of the sleep here?

Where does this range come from?  Is there a datasheet?

'\n' here.

> +	serdev_device_write_flush(mcu->serdev);
> +	ret = serdev_device_write_buf(mcu->serdev, cmd, size);
> +	if (ret < 0)
> +		goto exit;
> +
> +	serdev_device_wait_until_sent(mcu->serdev, IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
> +	ret = wait_for_completion_timeout(&mcu->reply->received,
> +					  IEI_WT61P803_PUZZLE_GENERAL_TIMEOUT);
> +	if (ret == 0) {
> +		dev_err(dev, "Command reply receive timeout\n");

Can you turn this into a proper English sentence please?

Perhaps "Timeout command received" or "Command timed out"?

> +		ret = -ETIMEDOUT;
> +		goto exit;

Tell us the goto's intention, so 'unlock' here.

> +	}
> +
> +	*reply_size = mcu->reply->size;
> +	/* Copy the received data, as it will not be available after a new frame is received */
> +	memcpy(reply_data, mcu->reply->data, mcu->reply->size);
> +	ret = 0;

read/write calls usually return the size of data read/written.

'\n' here.  This code is very bunched up/hard to read.

> +exit:
> +	mutex_unlock(&mcu->reply_lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(iei_wt61p803_puzzle_write_command);
> +
> +static int iei_wt61p803_puzzle_buzzer(struct iei_wt61p803_puzzle *mcu, bool long_beep)
> +{
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char buzzer_cmd[4] = {};
> +	size_t reply_size;
> +	int ret;
> +
> +	buzzer_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
> +	buzzer_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE;
> +	buzzer_cmd[2] = long_beep ? '3' : '2'; /* Buzzer 1.5 / 0.5 second beep */
> +
> +	mutex_lock(&mcu->lock);
> +	ret = iei_wt61p803_puzzle_write_command(mcu, buzzer_cmd, sizeof(buzzer_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto exit;
> +
> +	if (reply_size != 3) {
> +		ret = -EIO;
> +		goto exit;
> +	}
> +
> +	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
> +	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
> +	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
> +		ret = -EPROTO;
> +		goto exit;
> +	}

Why does all this processing have to be conducted under a lock?

'\n'

> +exit:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_get_version(struct iei_wt61p803_puzzle *mcu)
> +{
> +	unsigned char version_cmd[3] = {

As there are multiple 'version's, please name them all.

Is this the 'hw_version'?

> +		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
> +		IEI_WT61P803_PUZZLE_CMD_OTHER_VERSION,
> +	};
> +	unsigned char build_info_cmd[3] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
> +		IEI_WT61P803_PUZZLE_CMD_OTHER_BUILD,
> +	};
> +	unsigned char bootloader_mode_cmd[3] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
> +		IEI_WT61P803_PUZZLE_CMD_OTHER_BOOTLOADER_MODE,
> +	};
> +	unsigned char protocol_version_cmd[3] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER,
> +		IEI_WT61P803_PUZZLE_CMD_OTHER_PROTOCOL_VERSION,
> +	};
> +	unsigned char *rb = mcu->response_buffer;
> +	size_t reply_size;
> +	int ret;
> +
> +	mutex_lock(&mcu->lock);
> +
> +	ret = iei_wt61p803_puzzle_write_command(mcu, version_cmd, sizeof(version_cmd),
> +						rb, &reply_size);
> +	if (ret)
> +		goto err;

This is different again.  Please use 'unlock' throughout.

'\n' after all of these (ret) checks please.

Sometimes it's there, other times it's not.  Please be consistent.

> +	if (reply_size < 7) {
> +		ret = -EIO;
> +		goto err;
> +	}
> +	sprintf(mcu->version.version, "v%c.%.3s", rb[2], &rb[3]);
> +
> +	ret = iei_wt61p803_puzzle_write_command(mcu, build_info_cmd,
> +						sizeof(build_info_cmd),	rb,
> +						&reply_size);
> +	if (ret)
> +		goto err;
> +	if (reply_size < 15) {
> +		ret = -EIO;
> +		goto err;
> +	}

I don't like this setup.

Please change the API so that everything is encoded in ret.

If ret is less than 0 then an error occurred else encode the
reply_size into it.

> +	sprintf(mcu->version.build_info, "%c%c/%c%c/%.4s %c%c:%c%c",
> +		rb[8], rb[9], rb[6], rb[7], &rb[2], rb[10], rb[11],
> +		rb[12], rb[13]);
> +
> +	ret = iei_wt61p803_puzzle_write_command(mcu, bootloader_mode_cmd,
> +						sizeof(bootloader_mode_cmd), rb,
> +						&reply_size);
> +	if (ret)
> +		goto err;
> +	if (reply_size < 4) {
> +		ret = -EIO;
> +		goto err;
> +	}
> +	if (rb[2] == IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_APPS)
> +		mcu->version.bootloader_mode = false;
> +	else if (rb[2] == IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_BOOTLOADER)
> +		mcu->version.bootloader_mode = true;

You can drop one of these checks, no?

If bootloader_mode is already initialised you only need the 2nd one.

If it's not, just 'else bootloader_mode = false'.

> +	ret = iei_wt61p803_puzzle_write_command(mcu, protocol_version_cmd,
> +						sizeof(protocol_version_cmd), rb,
> +						&reply_size);
> +	if (ret)
> +		goto err;
> +	if (reply_size < 9) {
> +		ret = -EIO;
> +		goto err;
> +	}
> +	sprintf(mcu->version.protocol_version, "v%c.%c%c%c%c%c",
> +		rb[7], rb[6], rb[5], rb[4], rb[3], rb[2]);

'\n'

> +err:

'unlock'

> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_get_mcu_status(struct iei_wt61p803_puzzle *mcu)
> +{
> +	unsigned char mcu_status_cmd[5] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_START,
> +		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER,
> +		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS,
> +		IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS,
> +	};
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	size_t reply_size;
> +	int ret;
> +
> +	mutex_lock(&mcu->lock);
> +	ret = iei_wt61p803_puzzle_write_command(mcu, mcu_status_cmd, sizeof(mcu_status_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto exit;
> +	if (reply_size < 20) {
> +		ret = -EIO;
> +		goto exit;
> +	}
> +
> +	/* Response format:
> +	 * (IDX	RESPONSE)
> +	 * 0	@
> +	 * 1	O
> +	 * 2	S
> +	 * 3	S
> +	 * ...
> +	 * 5	AC Recovery Status Flag
> +	 * ...
> +	 * 10	Power Loss Recovery
> +	 * ...
> +	 * 19	Power Status (system power on method)
> +	 * 20	XOR checksum
> +	 */
> +	if (resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
> +	    resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER &&
> +	    resp_buf[2] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS &&
> +	    resp_buf[3] == IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS) {
> +		mcu->status.ac_recovery_status_flag = resp_buf[5];
> +		mcu->status.power_loss_recovery = resp_buf[10];
> +		mcu->status.power_status = resp_buf[19];
> +	}

'\n'

> +exit:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_get_serial_number(struct iei_wt61p803_puzzle *mcu)
> +{
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char serial_number_cmd[5] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> +		IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> +		0x00,	/* EEPROM read address */
> +		0x24,	/* Data length */

All of the magic numbers in this driver should be defined ideally.

> +	};
> +	size_t reply_size;
> +	int ret;
> +
> +	mutex_lock(&mcu->lock);
> +	ret = iei_wt61p803_puzzle_write_command(mcu, serial_number_cmd,
> +						sizeof(serial_number_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto err;
> +
> +	if (reply_size < IEI_WT61P803_PUZZLE_SN_LENGTH + 4) {

Why 4?  Probably better to define it.

> +		ret = -EIO;
> +		goto err;
> +	}
> +
> +	sprintf(mcu->version.serial_number, "%.*s",
> +		IEI_WT61P803_PUZZLE_SN_LENGTH, resp_buf + 4);

'\n'

> +err:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_write_serial_number(struct iei_wt61p803_puzzle *mcu,
> +						   unsigned char serial_number[36])
> +{
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char serial_number_header[4] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> +		IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> +		0x00,	/* EEPROM write address */
> +		0xC,	/* Data length */
> +	};
> +	unsigned char serial_number_cmd[4 + 12 + 1]; /* header, serial number, XOR checksum */
> +	int ret, sn_counter;
> +	size_t reply_size;
> +
> +	/* The MCU can only handle 22 byte messages, send the S/N in 12 byte chunks */

Byte

> +	mutex_lock(&mcu->lock);
> +	for (sn_counter = 0; sn_counter < 3; sn_counter++) {

Why 3?  Please define it.

> +		serial_number_header[2] = 0x0 + 0xC * sn_counter;

Define all magic numbers please.

I won't mention this again from here.

> +		memcpy(serial_number_cmd, serial_number_header, sizeof(serial_number_header));
> +		memcpy(serial_number_cmd + sizeof(serial_number_header),
> +		       serial_number + 0xC * sn_counter, 0xC);
> +
> +		ret = iei_wt61p803_puzzle_write_command(mcu, serial_number_cmd,
> +							sizeof(serial_number_cmd),
> +							resp_buf, &reply_size);
> +		if (ret)
> +			goto err;
> +		if (reply_size != 3) {
> +			ret = -EIO;
> +			goto err;
> +		}
> +		if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
> +		      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
> +		      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
> +			ret = -EPROTO;
> +			goto err;
> +		}
> +	}
> +
> +	sprintf(mcu->version.serial_number, "%.*s",
> +		IEI_WT61P803_PUZZLE_SN_LENGTH, serial_number);
> +err:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_get_mac_address(struct iei_wt61p803_puzzle *mcu, int index)
> +{
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char mac_address_cmd[5] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> +		IEI_WT61P803_PUZZLE_CMD_EEPROM_READ,
> +		0x00,	/* EEPROM read address */
> +		0x11,	/* Data length */
> +	};
> +	size_t reply_size;
> +	int ret;
> +
> +	mutex_lock(&mcu->lock);
> +	mac_address_cmd[2] = 0x24 + 0x11 * index;
> +
> +	ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
> +						sizeof(mac_address_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto err;
> +
> +	if (reply_size < 22) {
> +		ret = -EIO;
> +		goto err;
> +	}
> +
> +	sprintf(mcu->version.mac_address[index], "%.*s",
> +		IEI_WT61P803_PUZZLE_MAC_LENGTH, resp_buf + 4);
> +err:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int
> +iei_wt61p803_puzzle_write_mac_address(struct iei_wt61p803_puzzle *mcu,
> +				      unsigned char mac_address[IEI_WT61P803_PUZZLE_MAC_LENGTH],
> +				      int mac_address_idx)
> +{
> +	unsigned char mac_address_cmd[4 + IEI_WT61P803_PUZZLE_MAC_LENGTH + 1];
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char mac_address_header[4] = {
> +		IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM,
> +		IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE,
> +		0x00,	/* EEPROM write address */
> +		0x11,	/* Data length */
> +	};
> +	size_t reply_size;
> +	int ret;
> +
> +	if (mac_address_idx < 0 || mac_address_idx >= IEI_WT61P803_PUZZLE_NB_MAC)
> +		return -EINVAL;
> +
> +	mac_address_header[2] = 0x24 + 0x11 * mac_address_idx;
> +
> +	/* Concat mac_address_header, mac_address to mac_address_cmd */
> +	memcpy(mac_address_cmd, mac_address_header, sizeof(mac_address_header));
> +	memcpy(mac_address_cmd + sizeof(mac_address_header), mac_address,
> +	       IEI_WT61P803_PUZZLE_MAC_LENGTH);
> +
> +	mutex_lock(&mcu->lock);
> +	ret = iei_wt61p803_puzzle_write_command(mcu, mac_address_cmd,
> +						sizeof(mac_address_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto err;
> +	if (reply_size != 3) {
> +		ret = -EIO;
> +		goto err;
> +	}
> +	if (!(resp_buf[0] == IEI_WT61P803_PUZZLE_CMD_HEADER_START &&
> +	      resp_buf[1] == IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK &&
> +	      resp_buf[2] == IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK)) {
> +		ret = -EPROTO;
> +		goto err;
> +	}
> +
> +	sprintf(mcu->version.mac_address[mac_address_idx], "%.*s",
> +		IEI_WT61P803_PUZZLE_MAC_LENGTH, mac_address);
> +err:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_write_power_loss_recovery(struct iei_wt61p803_puzzle *mcu,
> +							 int power_loss_recovery_action)
> +{
> +	unsigned char *resp_buf = mcu->response_buffer;
> +	unsigned char power_loss_recovery_cmd[5] = {};
> +	size_t reply_size;
> +	int ret;
> +
> +	if (power_loss_recovery_action < 0 || power_loss_recovery_action > 4)

What are the 5 actions?  Are they mentioned/defined/enumed anywhere?

> +		return -EINVAL;
> +
> +	power_loss_recovery_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START;
> +	power_loss_recovery_cmd[1] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER;
> +	power_loss_recovery_cmd[2] = IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS;
> +	power_loss_recovery_cmd[3] = hex_asc[power_loss_recovery_action];
> +
> +	mutex_lock(&mcu->lock);
> +	ret = iei_wt61p803_puzzle_write_command(mcu, power_loss_recovery_cmd,
> +						sizeof(power_loss_recovery_cmd),
> +						resp_buf, &reply_size);
> +	if (ret)
> +		goto exit;
> +	mcu->status.power_loss_recovery = power_loss_recovery_action;
> +exit:
> +	mutex_unlock(&mcu->lock);
> +	return ret;
> +}

###########################################
###########################################

> +#define to_puzzle_dev_attr(_attr) \
> +	container_of(_attr, struct iei_wt61p803_puzzle_device_attribute, dev_attr)

Place this at the top of the file please.

> +static ssize_t show_output(struct device *dev,
> +			   struct device_attribute *attr, char *buf)
> +{
> +	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
> +	struct iei_wt61p803_puzzle_device_attribute *pattr = to_puzzle_dev_attr(attr);
> +	int ret;
> +
> +	switch (pattr->type) {
> +	case IEI_WT61P803_PUZZLE_VERSION:
> +		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.version);
> +	case IEI_WT61P803_PUZZLE_BUILD_INFO:
> +		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.build_info);
> +	case IEI_WT61P803_PUZZLE_BOOTLOADER_MODE:
> +		return scnprintf(buf, PAGE_SIZE, "%d\n", mcu->version.bootloader_mode);
> +	case IEI_WT61P803_PUZZLE_PROTOCOL_VERSION:
> +		return scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.protocol_version);
> +	case IEI_WT61P803_PUZZLE_SERIAL_NUMBER:
> +		ret = iei_wt61p803_puzzle_get_serial_number(mcu);
> +		if (!ret)
> +			ret = scnprintf(buf, PAGE_SIZE, "%s\n", mcu->version.serial_number);
> +		else
> +			ret = 0;
> +		return ret;
> +	case IEI_WT61P803_PUZZLE_MAC_ADDRESS:
> +		ret = iei_wt61p803_puzzle_get_mac_address(mcu, pattr->index);
> +		if (!ret)
> +			ret = scnprintf(buf, PAGE_SIZE, "%s\n",
> +					mcu->version.mac_address[pattr->index]);
> +		else
> +			ret = 0;
> +		return ret;
> +	case IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS:
> +	case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
> +	case IEI_WT61P803_PUZZLE_POWER_STATUS:
> +		ret = iei_wt61p803_puzzle_get_mcu_status(mcu);
> +		if (ret)
> +			return ret;
> +
> +		mutex_lock(&mcu->lock);
> +		switch (pattr->type) {
> +		case IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS:
> +			ret = scnprintf(buf, PAGE_SIZE, "%x\n",
> +					mcu->status.ac_recovery_status_flag);
> +			break;
> +		case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
> +			ret = scnprintf(buf, PAGE_SIZE, "%x\n", mcu->status.power_loss_recovery);
> +			break;
> +		case IEI_WT61P803_PUZZLE_POWER_STATUS:
> +			ret = scnprintf(buf, PAGE_SIZE, "%x\n", mcu->status.power_status);
> +			break;
> +		default:
> +			ret = 0;
> +			break;
> +		}
> +		mutex_unlock(&mcu->lock);
> +		return ret;
> +	default:
> +		return 0;
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t store_output(struct device *dev,
> +			    struct device_attribute *attr,
> +			    const char *buf, size_t len)
> +{
> +	unsigned char serial_number[IEI_WT61P803_PUZZLE_SN_LENGTH];
> +	unsigned char mac_address[IEI_WT61P803_PUZZLE_MAC_LENGTH];
> +	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
> +	struct iei_wt61p803_puzzle_device_attribute *pattr = to_puzzle_dev_attr(attr);
> +	int power_loss_recovery_action = 0;
> +	int ret;
> +
> +	switch (pattr->type) {
> +	case IEI_WT61P803_PUZZLE_SERIAL_NUMBER:
> +		if (len != (size_t)(IEI_WT61P803_PUZZLE_SN_LENGTH + 1))
> +			return -EINVAL;
> +		memcpy(serial_number, buf, sizeof(serial_number));
> +		ret = iei_wt61p803_puzzle_write_serial_number(mcu, serial_number);
> +		if (ret)
> +			return ret;
> +		return len;
> +	case IEI_WT61P803_PUZZLE_MAC_ADDRESS:
> +		if (len != (size_t)(IEI_WT61P803_PUZZLE_MAC_LENGTH + 1))
> +			return -EINVAL;
> +
> +		memcpy(mac_address, buf, sizeof(mac_address));
> +
> +		if (strlen(attr->attr.name) != 13)
> +			return -EIO;
> +
> +		ret = iei_wt61p803_puzzle_write_mac_address(mcu, mac_address, pattr->index);
> +		if (ret)
> +			return ret;
> +		return len;
> +	case IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY:
> +		ret = kstrtoint(buf, 10, &power_loss_recovery_action);
> +		if (ret)
> +			return ret;
> +		ret = iei_wt61p803_puzzle_write_power_loss_recovery(mcu,
> +								    power_loss_recovery_action);
> +		if (ret)
> +			return ret;
> +		return len;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +#define IEI_WT61P803_PUZZLE_ATTR(_name, _mode, _show, _store, _type, _index) \
> +	struct iei_wt61p803_puzzle_device_attribute dev_attr_##_name = \
> +		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
> +		  .type		= _type, \
> +		  .index	= _index }
> +
> +#define IEI_WT61P803_PUZZLE_ATTR_RO(_name, _type, _id) \
> +	IEI_WT61P803_PUZZLE_ATTR(_name, 0444, show_output, NULL, _type, _id)
> +
> +#define IEI_WT61P803_PUZZLE_ATTR_RW(_name, _type, _id) \
> +	IEI_WT61P803_PUZZLE_ATTR(_name, 0644, show_output, store_output, _type, _id)
> +
> +static IEI_WT61P803_PUZZLE_ATTR_RO(version, IEI_WT61P803_PUZZLE_VERSION, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RO(build_info, IEI_WT61P803_PUZZLE_BUILD_INFO, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RO(bootloader_mode, IEI_WT61P803_PUZZLE_BOOTLOADER_MODE, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RO(protocol_version, IEI_WT61P803_PUZZLE_PROTOCOL_VERSION, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(serial_number, IEI_WT61P803_PUZZLE_SERIAL_NUMBER, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_0, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_1, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 1);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_2, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 2);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_3, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 3);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_4, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 4);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_5, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 5);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_6, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 6);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(mac_address_7, IEI_WT61P803_PUZZLE_MAC_ADDRESS, 7);
> +static IEI_WT61P803_PUZZLE_ATTR_RO(ac_recovery_status, IEI_WT61P803_PUZZLE_AC_RECOVERY_STATUS, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RW(power_loss_recovery, IEI_WT61P803_PUZZLE_POWER_LOSS_RECOVERY, 0);
> +static IEI_WT61P803_PUZZLE_ATTR_RO(power_status, IEI_WT61P803_PUZZLE_POWER_STATUS, 0);
> +
> +static struct attribute *iei_wt61p803_puzzle_attrs[] = {
> +	&dev_attr_version.dev_attr.attr,
> +	&dev_attr_build_info.dev_attr.attr,
> +	&dev_attr_bootloader_mode.dev_attr.attr,
> +	&dev_attr_protocol_version.dev_attr.attr,
> +	&dev_attr_serial_number.dev_attr.attr,
> +	&dev_attr_mac_address_0.dev_attr.attr,
> +	&dev_attr_mac_address_1.dev_attr.attr,
> +	&dev_attr_mac_address_2.dev_attr.attr,
> +	&dev_attr_mac_address_3.dev_attr.attr,
> +	&dev_attr_mac_address_4.dev_attr.attr,
> +	&dev_attr_mac_address_5.dev_attr.attr,
> +	&dev_attr_mac_address_6.dev_attr.attr,
> +	&dev_attr_mac_address_7.dev_attr.attr,
> +	&dev_attr_ac_recovery_status.dev_attr.attr,
> +	&dev_attr_power_loss_recovery.dev_attr.attr,
> +	&dev_attr_power_status.dev_attr.attr,
> +	NULL
> +};
> +ATTRIBUTE_GROUPS(iei_wt61p803_puzzle);
> +
> +static int iei_wt61p803_puzzle_sysfs_create(struct device *dev,
> +					    struct iei_wt61p803_puzzle *mcu)

Why do you need 'mcu' here?

> +{
> +	int ret;
> +
> +	ret = sysfs_create_groups(&mcu->dev->kobj, iei_wt61p803_puzzle_groups);
> +	if (ret)
> +		mfd_remove_devices(mcu->dev);

You can't remove devices you haven't added!

> +	return ret;
> +}
> +
> +static int iei_wt61p803_puzzle_sysfs_remove(struct device *dev,
> +					    struct iei_wt61p803_puzzle *mcu)
> +{
> +	/* Remove sysfs groups */
> +	sysfs_remove_groups(&mcu->dev->kobj, iei_wt61p803_puzzle_groups);
> +	mfd_remove_devices(mcu->dev);
> +
> +	return 0;
> +}

###########################################
###########################################

> +static int iei_wt61p803_puzzle_probe(struct serdev_device *serdev)
> +{
> +	struct device *dev = &serdev->dev;
> +	struct iei_wt61p803_puzzle *mcu;
> +	u32 baud;
> +	int ret;
> +
> +	/* Read the baud rate from 'current-speed', because the MCU supports different rates */
> +	if (device_property_read_u32(dev, "current-speed", &baud)) {
> +		dev_err(dev,
> +			"'current-speed' is not specified in device node\n");

Same line please.

"'current-speed' device tree property not found"

> +		return -EINVAL;
> +	}
> +	dev_dbg(dev, "Driver baud rate: %d\n", baud);
> +
> +	/* Allocate the memory */

Drop this comment.

> +	mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
> +	if (!mcu)
> +		return -ENOMEM;
> +
> +	mcu->reply = devm_kzalloc(dev, sizeof(*mcu->reply), GFP_KERNEL);
> +	if (!mcu->reply)
> +		return -ENOMEM;
> +
> +	/* Initialize device struct data */

And this please.

> +	mcu->serdev = serdev;
> +	mcu->dev = dev;

Doesn't serdev already have dev?

If so, you don't need to store both.

> +	init_completion(&mcu->reply->received);
> +	mutex_init(&mcu->reply_lock);

Why can't you use 'lock' in all cases?

> +	mutex_init(&mcu->lock);
> +
> +	/* Setup UART interface */

Drop the comment please.  It's superfluous.

> +	serdev_device_set_drvdata(serdev, mcu);
> +	serdev_device_set_client_ops(serdev, &iei_wt61p803_puzzle_serdev_device_ops);
> +	ret = devm_serdev_device_open(dev, serdev);
> +	if (ret)
> +		return ret;

'\n'

> +	serdev_device_set_baudrate(serdev, baud);
> +	serdev_device_set_flow_control(serdev, false);
> +	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
> +	if (ret) {
> +		dev_err(dev, "Failed to set parity\n");
> +		return ret;
> +	}
> +
> +	ret = iei_wt61p803_puzzle_get_version(mcu);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(dev, "MCU version: %s\n", mcu->version.version);
> +	dev_dbg(dev, "MCU firmware build info: %s\n", mcu->version.build_info);
> +	dev_dbg(dev, "MCU in bootloader mode: %s\n",
> +		mcu->version.bootloader_mode ? "true" : "false");
> +	dev_dbg(dev, "MCU protocol version: %s\n", mcu->version.protocol_version);
> +
> +	if (device_property_read_bool(dev, "enable-beep")) {
> +		ret = iei_wt61p803_puzzle_buzzer(mcu, false);

You're beeping just because it's enabled?

> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = iei_wt61p803_puzzle_sysfs_create(dev, mcu);
> +	if (ret)
> +		return ret;
> +
> +	return devm_of_platform_populate(dev);
> +}
> +
> +static void iei_wt61p803_puzzle_remove(struct serdev_device *serdev)
> +{
> +	struct device *dev = &serdev->dev;
> +	struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev);
> +
> +	iei_wt61p803_puzzle_sysfs_remove(dev, mcu);
> +}
> +
> +static const struct of_device_id iei_wt61p803_puzzle_dt_ids[] = {
> +	{ .compatible = "iei,wt61p803-puzzle" },
> +	{ }
> +};
> +

Remove this line.

> +MODULE_DEVICE_TABLE(of, iei_wt61p803_puzzle_dt_ids);
> +
> +static struct serdev_device_driver iei_wt61p803_puzzle_drv = {
> +	.probe			= iei_wt61p803_puzzle_probe,
> +	.remove			= iei_wt61p803_puzzle_remove,

No need to tab these out to match the subordinate structure.

> +	.driver = {
> +		.name		= "iei-wt61p803-puzzle",
> +		.of_match_table	= iei_wt61p803_puzzle_dt_ids,
> +	},
> +};
> +

Remove this line.

> +module_serdev_device_driver(iei_wt61p803_puzzle_drv);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Luka Kovacic <luka.kovacic@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("IEI WT61P803 PUZZLE MCU Driver");
> diff --git a/include/linux/mfd/iei-wt61p803-puzzle.h b/include/linux/mfd/iei-wt61p803-puzzle.h
> new file mode 100644
> index 000000000000..7f2da5887dae
> --- /dev/null
> +++ b/include/linux/mfd/iei-wt61p803-puzzle.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* IEI WT61P803 PUZZLE MCU Driver

Non-conformant multi-line comment

> + * System management microcontroller for fan control, temperature sensor reading,
> + * LED control and system identification on IEI Puzzle series ARM-based appliances.
> + *
> + * Copyright (C) 2020 Sartura Ltd.
> + * Author: Luka Kovacic <luka.kovacic@xxxxxxxxxx>
> + */
> +
> +#ifndef _MFD_IEI_WT61P803_PUZZLE_H_
> +#define _MFD_IEI_WT61P803_PUZZLE_H_
> +
> +#define IEI_WT61P803_PUZZLE_BUF_SIZE 512
> +
> +/* Command magic numbers */
> +#define IEI_WT61P803_PUZZLE_CMD_HEADER_START		0x40 /* @ */
> +#define IEI_WT61P803_PUZZLE_CMD_HEADER_START_OTHER	0x25 /* % */
> +#define IEI_WT61P803_PUZZLE_CMD_HEADER_EEPROM		0xF7
> +
> +#define IEI_WT61P803_PUZZLE_CMD_RESPONSE_OK		0x30 /* 0 */
> +#define IEI_WT61P803_PUZZLE_CHECKSUM_RESPONSE_OK	0x70
> +
> +#define IEI_WT61P803_PUZZLE_CMD_EEPROM_READ		0xA1
> +#define IEI_WT61P803_PUZZLE_CMD_EEPROM_WRITE		0xA0
> +
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_VERSION		0x56 /* V */
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_BUILD		0x42 /* B */
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_BOOTLOADER_MODE	0x4D /* M */
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_BOOTLOADER	0x30
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_MODE_APPS		0x31
> +#define IEI_WT61P803_PUZZLE_CMD_OTHER_PROTOCOL_VERSION	0x50 /* P */
> +
> +#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_SINGLE		0x43 /* C */
> +#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER		0x4F /* O */
> +#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_STATUS	0x53 /* S */
> +#define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */
> +
> +#define IEI_WT61P803_PUZZLE_CMD_LED			0x52 /* R */
> +#define IEI_WT61P803_PUZZLE_CMD_LED_POWER		0x31 /* 1 */
> +
> +#define IEI_WT61P803_PUZZLE_CMD_TEMP			0x54 /* T */
> +#define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL		0x41 /* A */
> +
> +#define IEI_WT61P803_PUZZLE_CMD_FAN			0x46 /* F */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_READ		0x5A /* Z */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_WRITE		0x57 /* W */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM_BASE		0x30
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM_BASE		0x41 /* A */
> +
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_PWM(x) (IEI_WT61P803_PUZZLE_CMD_FAN_PWM_BASE + (x)) /* 0 - 1 */
> +#define IEI_WT61P803_PUZZLE_CMD_FAN_RPM(x) (IEI_WT61P803_PUZZLE_CMD_FAN_RPM_BASE + (x)) /* 0 - 5 */
> +
> +struct iei_wt61p803_puzzle_mcu_version;
> +struct iei_wt61p803_puzzle_reply;
> +struct iei_wt61p803_puzzle;
> +
> +int iei_wt61p803_puzzle_write_command_watchdog(struct iei_wt61p803_puzzle *mcu,
> +					       unsigned char *cmd, size_t size,
> +					       unsigned char *reply_data, size_t *reply_size,
> +					       int retry_count);
> +
> +int iei_wt61p803_puzzle_write_command(struct iei_wt61p803_puzzle *mcu,
> +				      unsigned char *cmd, size_t size,
> +				      unsigned char *reply_data, size_t *reply_size);
> +
> +#endif /* _MFD_IEI_WT61P803_PUZZLE_H_ */

-- 
Lee Jones [李琼斯]
Senior Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog



[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