Skip to contentSkip to conversation

Turris Omnia MCU driver

PATCH Superseded Marek Behún 5 participants 31 messages Sep 19, 2023 – Sep 25, 2023

Subsystems (6)

v2 of 13 Superseded Latest is v13 →

lore
Share & tools
lore

Marek Behún

author maintainer
#0 lore raw

v2 cover Turris Omnia MCU driver

Hello Arnd, Gregorgy and others,

this is v2 of series adding support for Turris Omnia MCU. See the cover letter from v1:
https://patchwork.kernel.org/project/linux-soc/cover/[email protected]/

I am also sending this to specific subsystem maintainers as requested by Krzysztof.

Changes since v1:
- incorporated small changes requested by Krzysztof Kozlowski
- in order to make reviewing simpler, I split the second patch, which implements the MCU driver, into four incremental patches:
- patch 2/7 adds basic driver skeleton
- patch 3/7 adds GPIO support
- patch 4/7 adds board poweroff and wakeup support
- patch 5/7 adds MCU watchdog support

Marek

Marek Behún (7):
dt-bindings: arm: add cznic,turris-omnia-mcu binding
platform: cznic: Add preliminary support for Turris Omnia MCU
platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs
platform: cznic: turris-omnia-mcu: Add support for poweroff and wakeup
platform: cznic: turris-omnia-mcu: Add support for MCU watchdog
ARM: dts: turris-omnia: Add MCU system-controller node
ARM: dts: turris-omnia: Add GPIO key node for front button

 .../sysfs-bus-i2c-devices-turris-omnia-mcu    |  77 ++
 .../bindings/arm/cznic,turris-omnia-mcu.yaml  |  67 ++
 MAINTAINERS                                   |   4 +
 .../dts/marvell/armada-385-turris-omnia.dts   |  35 +-
 drivers/platform/Kconfig                      |   2 +
 drivers/platform/Makefile                     |   1 +
 drivers/platform/cznic/Kconfig                |  47 +
 drivers/platform/cznic/Makefile               |   7 +
 .../platform/cznic/turris-omnia-mcu-base.c    | 273 +++++
 .../platform/cznic/turris-omnia-mcu-gpio.c    | 971 ++++++++++++++++++
 .../cznic/turris-omnia-mcu-sys-off-wakeup.c   | 245 +++++
 .../cznic/turris-omnia-mcu-watchdog.c         | 126 +++
 drivers/platform/cznic/turris-omnia-mcu.h     | 164 +++
 include/linux/turris-omnia-mcu-interface.h    | 194 ++++
 14 files changed, 2212 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
 create mode 100644 Documentation/devicetree/bindings/arm/cznic,turris-omnia-mcu.yaml
 create mode 100644 drivers/platform/cznic/Kconfig
 create mode 100644 drivers/platform/cznic/Makefile
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-base.c
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-gpio.c
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu.h
 create mode 100644 include/linux/turris-omnia-mcu-interface.h
Signature · 1 line
2.41.0
author maintainer
#1 lore raw

PATCH v2 1/7 dt-bindings: arm: add cznic,turris-omnia-mcu binding

Add binding for cznic,turris-omnia-mcu, the device-tree node representing the system-controller features provided by the MCU on the Turris Omnia router.

---
 .../bindings/arm/cznic,turris-omnia-mcu.yaml  | 67 +++++++++++++++++++
 MAINTAINERS                                   |  1 +
 2 files changed, 68 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/cznic,turris-omnia-mcu.yaml
Documentation/devicetree/bindings/arm/cznic,turris-omnia-mcu.yaml new file +67 -0
new file mode 100644
index 000000000000..ea1fd0117bb8
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/cznic,turris-omnia-mcu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: CZ.NIC's Turris Omnia MCU
+
+maintainers:
+  - Marek Behún <[email protected]>
+
+description:
+  The MCU on Turris Omnia acts as a system controller providing additional
+  GPIOs, interrupts, watchdog, system power off and wakeup configuration.
+
+properties:
+  compatible:
+    const: cznic,turris-omnia-mcu
+
+  reg:
+    description: MCU I2C slave address
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  interrupt-controller: true
+
+  '#interrupt-cells':
+    const: 2
+
+  gpio-controller: true
+
+  '#gpio-cells':
+    const: 2
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-controller
+  - gpio-controller
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        system-controller@2a {
+            compatible = "cznic,turris-omnia-mcu";
+            reg = <0x2a>;
+
+            interrupt-parent = <&gpio1>;
+            interrupts = <11 IRQ_TYPE_NONE>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+
+            interrupt-controller;
+            #interrupt-cells = <2>;
+        };
+    };
MAINTAINERS +1 -0
index bf0f54c24f81..0a4d7d6ed63a 100644
@@ -2091,6 +2091,7 @@ W:	https://www.turris.cz/
 F:	Documentation/ABI/testing/debugfs-moxtet
 F:	Documentation/ABI/testing/sysfs-bus-moxtet-devices
 F:	Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm
+F:	Documentation/devicetree/bindings/arm/cznic,turris-omnia-mcu.yaml
 F:	Documentation/devicetree/bindings/bus/moxtet.txt
 F:	Documentation/devicetree/bindings/firmware/cznic,turris-mox-rwtm.txt
 F:	Documentation/devicetree/bindings/gpio/gpio-moxtet.txt
Signature · 1 line
2.41.0
author maintainer
#3 lore raw

PATCH v2 2/7 platform: cznic: Add preliminary support for Turris Omnia MCU

In reply to Marek Behún

Add a new platform driver that exposes the features provided by the microcontroller on the Turris Omnia board.

This commit adds the basic skeleton for the driver.

---
 .../sysfs-bus-i2c-devices-turris-omnia-mcu    |  45 ++++
 MAINTAINERS                                   |   3 +
 drivers/platform/Kconfig                      |   2 +
 drivers/platform/Makefile                     |   1 +
 drivers/platform/cznic/Kconfig                |  26 ++
 drivers/platform/cznic/Makefile               |   4 +
 .../platform/cznic/turris-omnia-mcu-base.c    | 253 ++++++++++++++++++
 drivers/platform/cznic/turris-omnia-mcu.h     |  65 +++++
 include/linux/turris-omnia-mcu-interface.h    | 194 ++++++++++++++
 9 files changed, 593 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
 create mode 100644 drivers/platform/cznic/Kconfig
 create mode 100644 drivers/platform/cznic/Makefile
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-base.c
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu.h
 create mode 100644 include/linux/turris-omnia-mcu-interface.h
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu new file +45 -0
new file mode 100644
index 000000000000..69a4db7e20c0
@@ -0,0 +1,45 @@
+What:		/sys/bus/i2c/devices/<mcu_device>/fw_features
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RO) Newer versions of the microcontroller firmware report the
+		features they support. These can be read from this file. If the
+		MCU firmware is too old, this file reads 0x0.
+
+		Format: 0x%x.
+
+What:		/sys/bus/i2c/devices/<mcu_device>/fw_version_hash_application
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RO) Contains the version hash (commit hash) of the application
+		part of the microcontroller firmware.
+
+		Format: %s.
+
+What:		/sys/bus/i2c/devices/<mcu_device>/fw_version_hash_bootloader
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RO) Contains the version hash (commit hash) of the bootloader
+		part of the microcontroller firmware.
+
+		Format: %s.
+
+What:		/sys/bus/i2c/devices/<mcu_device>/mcu_type
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RO) Contains the microcontroller type (STM32, GD32, MKL).
+
+		Format: %s.
+
+What:		/sys/bus/i2c/devices/<mcu_device>/reset_selector
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RO) Contains the selected factory reset level, determined by
+		how long the rear reset button was held by the user during board
+		reset.
+
+		Format: %i.
MAINTAINERS +3 -0
index 0a4d7d6ed63a..1396743d5f9f 100644
@@ -2089,6 +2089,7 @@ M:	Marek Behún <[email protected]>
 S:	Maintained
 W:	https://www.turris.cz/
 F:	Documentation/ABI/testing/debugfs-moxtet
+F:	Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
 F:	Documentation/ABI/testing/sysfs-bus-moxtet-devices
 F:	Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm
 F:	Documentation/devicetree/bindings/arm/cznic,turris-omnia-mcu.yaml
@@ -2102,10 +2103,12 @@ F:	drivers/firmware/turris-mox-rwtm.c
 F:	drivers/gpio/gpio-moxtet.c
 F:	drivers/leds/leds-turris-omnia.c
 F:	drivers/mailbox/armada-37xx-rwtm-mailbox.c
+F:	drivers/platform/cznic/
 F:	drivers/watchdog/armada_37xx_wdt.c
 F:	include/dt-bindings/bus/moxtet.h
 F:	include/linux/armada-37xx-rwtm-mailbox.h
 F:	include/linux/moxtet.h
+F:	include/linux/turris-omnia-mcu-interface.h
 
 ARM/FARADAY FA526 PORT
 M:	Hans Ulli Kroll <[email protected]>
drivers/platform/Kconfig +2 -0
index 868b20361769..fef907a94001 100644
@@ -7,6 +7,8 @@ source "drivers/platform/goldfish/Kconfig"
 
 source "drivers/platform/chrome/Kconfig"
 
+source "drivers/platform/cznic/Kconfig"
+
 source "drivers/platform/mellanox/Kconfig"
 
 source "drivers/platform/olpc/Kconfig"
drivers/platform/Makefile +1 -0
index 41640172975a..cc66b5020a34 100644
@@ -11,3 +11,4 @@ obj-$(CONFIG_OLPC_EC)		+= olpc/
 obj-$(CONFIG_GOLDFISH)		+= goldfish/
 obj-$(CONFIG_CHROME_PLATFORMS)	+= chrome/
 obj-$(CONFIG_SURFACE_PLATFORMS)	+= surface/
+obj-$(CONFIG_CZNIC_PLATFORMS)	+= cznic/
drivers/platform/cznic/Kconfig new file +26 -0
new file mode 100644
index 000000000000..f8560ff9c1af
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# For a description of the syntax of this configuration file,
+# see Documentation/kbuild/kconfig-language.rst.
+#
+
+menuconfig CZNIC_PLATFORMS
+	bool "Platform support for CZ.NIC's Turris hardware"
+	depends on MACH_ARMADA_38X || COMPILE_TEST
+	help
+	  Say Y here to be able to choose driver support for CZ.NIC's Turris
+	  devices. This option alone does not add any kernel code.
+
+if CZNIC_PLATFORMS
+
+config TURRIS_OMNIA_MCU
+	tristate "Turris Omnia MCU driver"
+	depends on MACH_ARMADA_38X || COMPILE_TEST
+	depends on I2C
+	help
+	  Say Y here to add support for the features implemented by the
+	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
+	  To compile this driver as a module, choose M here; the module will be
+	  called turris-omnia-mcu.
+
+endif # CZNIC_PLATFORMS
drivers/platform/cznic/Makefile new file +4 -0
new file mode 100644
index 000000000000..4d0a9586538c
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
+turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
drivers/platform/cznic/turris-omnia-mcu-base.c new file +253 -0

253 lines not on this page · raw message

drivers/platform/cznic/turris-omnia-mcu.h new file +65 -0

65 lines not on this page · raw message

include/linux/turris-omnia-mcu-interface.h new file +194 -0

194 lines not on this page · raw message

Signature · 1 line
2.41.0
#4 lore raw

Re: [PATCH v2 2/7] platform: cznic: Add preliminary support for Turris Omnia MCU

Quoted 4 lines · Marek Behún

Add a new platform driver that exposes the features provided by the microcontroller on the Turris Omnia board.

This commit adds the basic skeleton for the driver.

Read "Submitting Patches" documentation (find "This patch" in it) and amend your commit message accordingly.

...

Review

Quoted 2 lines
(quoted diff) +2 -0
+Date:		August 2023
+KernelVersion:	6.6

Wrong and outdated. Use phb-crystall-ball to find the closest possible values for both parameters here.

Ditto for all others with the same issue.

Note, it likely might be part of v6.7, not earlier.

...

drivers/platform/Makefile obj-$(CONFIG_OLPC_EC) += olpc/ · line 14

Quoted 5 lines
drivers/platform/Makefile +1 -0
@@ -11,3 +11,4 @@ obj-$(CONFIG_OLPC_EC)		+= olpc/
 obj-$(CONFIG_GOLDFISH)		+= goldfish/
 obj-$(CONFIG_CHROME_PLATFORMS)	+= chrome/
 obj-$(CONFIG_SURFACE_PLATFORMS)	+= surface/
+obj-$(CONFIG_CZNIC_PLATFORMS)	+= cznic/

Why not ordered (to some extent) here (as you did in the other file)?

...

Quoted 1 line

+turris-omnia-mcu-objs := turris-omnia-mcu-base.o

objs is for user space tools. Kernel code should use m,y.

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 9

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+#include <linux/hex.h>
+#include <linux/module.h>

Missing types.h, sysfs.h, mod_devicetable.h, i2c.h, ...

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 22

Quoted 11 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +10 -0
+static int omnia_get_version_hash(struct omnia_mcu *mcu, bool bootloader,
+				  u8 *version)
+{
+	u8 reply[20];
+	int ret;
+
+	ret = omnia_cmd_read(mcu->client, bootloader ? CMD_GET_FW_VERSION_BOOT :
+						       CMD_GET_FW_VERSION_APP,
+			     reply, sizeof(reply));
 
+	if (ret < 0)

Can it return positive value? What would be the meaning of it?

drivers/platform/cznic/turris-omnia-mcu-base.c line 25

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+		return ret;
 
+	version[40] = '\0';

How do you know the version has enough space?

drivers/platform/cznic/turris-omnia-mcu-base.c line 36

Quoted 12 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +11 -0
+	bin2hex(version, reply, 20);
+
+	return 0;
+}
+
+static ssize_t fw_version_hash_show(struct device *dev, char *buf,
+				    bool bootloader)
+{
+	struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev));
+	u8 version[41];
 
+	int err;

In two near functions you already inconsistent in the naming of the return code variable. Be consistent across all your code, i.e. choose one name and use it everywhere.

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +6 -0
+	err = omnia_get_version_hash(mcu, bootloader, version);
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%s\n", version);
+}

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 66

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	return sysfs_emit(buf, "%#x\n", mcu->features);

"0" will be printed differently, is this on purpose?

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 85

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +4 -0
+	int ret = omnia_cmd_read_u8(to_i2c_client(dev), CMD_GET_RESET);
+
+	if (ret < 0)
+		return ret;

Better from maintenance perspective to have

	int ret;

	ret = omnia_cmd_read_u8(to_i2c_client(dev), CMD_GET_RESET);
	if (ret < 0)
		return ret;

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 97

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +7 -0
+static struct attribute *omnia_mcu_attrs[] = {
+	&dev_attr_fw_version_hash_application.attr,
+	&dev_attr_fw_version_hash_bootloader.attr,
+	&dev_attr_fw_features.attr,
+	&dev_attr_mcu_type.attr,
+	&dev_attr_reset_selector.attr,
 
+	NULL,

No comma for the terminator line. It will make your code robust at compile time against some misplaced entries.

drivers/platform/cznic/turris-omnia-mcu-base.c line 99

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+};
 
+

Unneeded blank line.

Quoted 1 line

+ATTRIBUTE_GROUPS(omnia_mcu);

...

Review

Quoted 1 line
(quoted diff) +1 -0
+	u8 version[41];

This magic sizes should go away. Use predefined constant, or allocate on the heap, depending on the case(s) you have.

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 154

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	int status;

My gosh, it's a _third_ name for the same!

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +3 -0
+	status = omnia_cmd_read_u16(mcu->client, CMD_GET_STATUS_WORD);
+	if (status < 0)
+		return status;

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 186

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	for (int i = 0; i < ARRAY_SIZE(features); ++i) {

Why signed?
Why pre-increment?

drivers/platform/cznic/turris-omnia-mcu-base.c line 187

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+		if (!(mcu->features & features[i].mask)) {

Wouldn't be better

		if (mcu->features & features[i].mask)
			continue;

?

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +4 -0
+			omnia_info_missing_feature(dev, features[i].name);
+			suggest_fw_upgrade = true;
+		}
+	}

...

Review

Quoted 2 lines
(quoted diff) +2 -0
+		dev_info(dev,
+			 "Consider upgrading MCU firmware with the omnia-mcutool utility.\n");

You have so-o many dev_info() calls, are you sure you not abusing use of that?

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 217

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +4 -0
+	if (ret) {
+		dev_err(dev, "Cannot determine MCU supported features: %d\n",
+			ret);
+		return ret;
		return dev_err_probe(...);
Quoted 1 line
(quoted diff) +1 -0
+	}

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 223

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +4 -0
+	if (!client->irq) {
+		dev_err(dev, "IRQ resource not found\n");
+		return -ENODATA;
+	}

Why you need to allocate memory, go through the initial checks, etc and then fail? What's wrong with checking this first?

Why -ENODATA?!

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 229

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+static const struct of_device_id of_omnia_mcu_match[] = {
+	{ .compatible = "cznic,turris-omnia-mcu", },

Inner comma is not needed.

drivers/platform/cznic/turris-omnia-mcu-base.c line 230

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	{},

No comma for the terminator entry.

Quoted 1 line

+};

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 234

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+static const struct i2c_device_id omnia_mcu_id[] = {
+	{ "turris-omnia-mcu", 0 },

', 0' part is redundant.

Quoted 2 lines
(quoted diff) +2 -0
+	{ }
+};

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 248

Quoted 11 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +10 -0
+static struct i2c_driver omnia_mcu_driver = {
+	.probe		= omnia_mcu_probe,
+	.id_table	= omnia_mcu_id,
+	.driver		= {
+		.name	= "turris-omnia-mcu",
+		.of_match_table = of_omnia_mcu_match,
+		.dev_groups = omnia_mcu_groups,
+	},
+};
 
+

Redundant blank line.

Quoted 1 line

+module_i2c_driver(omnia_mcu_driver);

...

drivers/platform/cznic/turris-omnia-mcu.h line 9

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu.h new file +2 -0
+#ifndef __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H
+#define __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H

WE_LIKE_VERY_LONG_AND_OVERFLOWED_DEFINITIONS_H!

...

Quoted 1 line

+#include <asm/byteorder.h>

This usually goes after linux/*.h.

Quoted 1 line

+#include <linux/i2c.h>

Missing types.h, errno.h, ...

+ blank line?

Quoted 1 line

+#include <linux/turris-omnia-mcu-interface.h>

...

drivers/platform/cznic/turris-omnia-mcu.h line 37

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu.h new file +2 -0
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (likely(ret == ARRAY_SIZE(msgs)))

Why likely()? Please, justify.

drivers/platform/cznic/turris-omnia-mcu.h line 42

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu.h new file +5 -0
+		return len;
 
+	else if (ret < 0)
+		return ret;
+	else
+		return -EIO;

In both cases the 'else' is redundant. Moreover, the usual pattern is to check for the errors first.

Quoted 1 line

+}

...

include/linux/turris-omnia-mcu-interface.h line 9

Quoted 2 lines
include/linux/turris-omnia-mcu-interface.h new file +2 -0
+#ifndef __INCLUDE_LINUX_TURRIS_OMNIA_MCU_INTERFACE_H
+#define __INCLUDE_LINUX_TURRIS_OMNIA_MCU_INTERFACE_H

My gosh!

...

include/linux/turris-omnia-mcu-interface.h line 13

Quoted 3 lines
include/linux/turris-omnia-mcu-interface.h new file +2 -0
+#include <linux/bits.h>
 
+enum commands_e {

Are you sure the name is unique enough / properly namespaced? Same Q to all enums.

...

include/linux/turris-omnia-mcu-interface.h line 96

Quoted 1 line
include/linux/turris-omnia-mcu-interface.h new file +1 -0
+	/*CTL_RESERVED		= BIT(2),*/

Missing spaces?
Also, needs a comment why this is commented out.

...

include/linux/turris-omnia-mcu-interface.h line 101

Quoted 1 line
include/linux/turris-omnia-mcu-interface.h new file +1 -0
+	CTL_BOOTLOADER		= BIT(7)

Add trailing comma.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#5 lore raw

Re: [PATCH v2 2/7] platform: cznic: Add preliminary support for Turris Omnia MCU

Hello Andy,

thank you for your review. I have some questions regarding your comments.

Quoted 8 lines · On Tue, 19 Sep 2023 15:29:08 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 4 lines · Marek Behún

Add a new platform driver that exposes the features provided by the microcontroller on the Turris Omnia board.

This commit adds the basic skeleton for the driver.

Read "Submitting Patches" documentation (find "This patch" in it) and amend your commit message accordingly.

OK.

Quoted 9 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+Date:		August 2023
+KernelVersion:	6.6  

Wrong and outdated. Use phb-crystall-ball to find the closest possible values for both parameters here.

Ditto for all others with the same issue.

Note, it likely might be part of v6.7, not earlier.

OK

Quoted 6 lines
Quoted 4 lines

obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
+obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/

Why not ordered (to some extent) here (as you did in the other file)?

Where should I put it? The other entries are not ordered, see
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/Makefile?h=v6.6-rc2

Quoted 3 lines
Quoted 1 line

+turris-omnia-mcu-objs := turris-omnia-mcu-base.o

objs is for user space tools. Kernel code should use m,y.

I don't understand. There are plenty driver using -objs. The driver consists of multiple C source files. I thought this should be done as
obj-$(CONFIG_XXX) += xxx.o
xxx-objs := xxx-a.o xxx-b.o xxx-c.o

If you choose CONFIG_XXX = y, then the driver will be compiled into kernel, if you choose CONFIG_XXX = m, then the driver will be a module xxx.ko.

Quoted 4 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 9

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+#include <linux/hex.h>
+#include <linux/module.h>  

Missing types.h, sysfs.h, mod_devicetable.h, i2c.h, ...

i2c.h comes from turris-omnia-mcu.h. The others I will fix. Does the the .c file also include all those headers if it includes a local header already?

Quoted 13 lines
Quoted 12 lines

+static int omnia_get_version_hash(struct omnia_mcu *mcu, bool bootloader,

drivers/platform/cznic/turris-omnia-mcu-base.c new file +6 -0
+				  u8 *version)
+{
+	u8 reply[20];
+	int ret;
+
+	ret = omnia_cmd_read(mcu->client, bootloader ?

CMD_GET_FW_VERSION_BOOT :
+n average development ti
CMD_GET_FW_VERSION_APP,

drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+			     reply, sizeof(reply));  
Quoted 3 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	if (ret < 0)  

Can it return positive value? What would be the meaning of it?

Originally I had it return 0 on success, but recently I've had a discussion about this same thing with LED subsystem maintainer about some LED patches, Lee Jones, who requested the _read function to return number of bytes read on success. See at
https://lore.kernel.org/linux-leds/[email protected]/
where he says:
Read and write APIs, even abstracted ones such as these generally return the number of bytes successfully read and written respectively. If you are going to normalise, then please do so against this standard.
I do not agree with him, since this is a local command accessor which only succeeds if all the bytes are read/written as requested. But I adjusted my code as per his request since he is the maintainer.

I will change the code back so that it returns 0 on success instead of the number of bytes written.

Quoted 5 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 25

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+		return ret;  
 
+	version[40] = '\0';  

How do you know the version has enough space?

The function is only use within the file, and both users know how much space it needs. But ok, I shall enforce it with the static keyword as
static int
omnia_get_version_hash(...,

			 u8 version[static 2*OMNIA_FW_VERSION_LEN + 1]);

Would that be okay?

Quoted 12 lines
Quoted 11 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +9 -0
+	bin2hex(version, reply, 20);
+
+	return 0;
+}
+
+static ssize_t fw_version_hash_show(struct device *dev, char *buf,
+				    bool bootloader)
+{
+	struct omnia_mcu *mcu =

i2c_get_clientdata(to_i2c_client(dev));

(quoted diff) +1 -0
+	u8 version[41];  
Quoted 5 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	int err;  

In two near functions you already inconsistent in the naming of the return code variable. Be consistent across all your code, i.e. choose one name and use it everywhere.

I use
ret - when the return value can also be positive and mean something err - when the return value is either 0 (no error) or negative errno
Is this inconsistent?

Quoted 9 lines
Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +6 -0
+	err = omnia_get_version_hash(mcu, bootloader, version);
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%s\n", version);
+}  

...

Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 66

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	return sysfs_emit(buf, "%#x\n", mcu->features);  

"0" will be printed differently, is this on purpose?

No, I wanted it to print 0x0, didn't know '#' would print just 0. Thanks.

Quoted 7 lines

Review

Quoted 4 lines
(quoted diff) +1 -0
+	int ret = omnia_cmd_read_u8(to_i2c_client(dev),

CMD_GET_RESET); +

(quoted diff) +2 -0
+	if (ret < 0)
+		return ret;  

Better from maintenance perspective to have

Quoted 5 lines
	int ret;

	ret = omnia_cmd_read_u8(to_i2c_client(dev), CMD_GET_RESET);
	if (ret < 0)
		return ret;

OK

Quoted 11 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 97

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +7 -0
+static struct attribute *omnia_mcu_attrs[] = {
+	&dev_attr_fw_version_hash_application.attr,
+	&dev_attr_fw_version_hash_bootloader.attr,
+	&dev_attr_fw_features.attr,
+	&dev_attr_mcu_type.attr,
+	&dev_attr_reset_selector.attr,  
 
+	NULL,  

No comma for the terminator line. It will make your code robust at compile time against some misplaced entries.

OK

Quoted 5 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 99

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+};  
 
+  

Unneeded blank line.

OK

Quoted 8 lines
Quoted 1 line

+ATTRIBUTE_GROUPS(omnia_mcu);

...

Review

Quoted 1 line
(quoted diff) +1 -0
+	u8 version[41];  

This magic sizes should go away. Use predefined constant, or allocate on the heap, depending on the case(s) you have.

OK

Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 154

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	int status;  

My gosh, it's a _third_ name for the same!

The variable holds value of the status register, which is needed at another point in this function. I thought naming it just "ret" would cause confusions... Do you really propose to rename it such?

Quoted 10 lines
Quoted 4 lines
(quoted diff) +1 -0
+	status = omnia_cmd_read_u16(mcu->client,

CMD_GET_STATUS_WORD);

drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+	if (status < 0)
+		return status;  

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 186

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	for (int i = 0; i < ARRAY_SIZE(features); ++i) {  

Why signed?

Because it is shorter and makes no difference and there are tons of
for (int i = 0; i < ARRAY_SIZE(x); i++)
in the kernel already. But if you really want, I can change it. Please let me know.

Quoted 1 line

Why pre-increment?

Again, it makes no difference, is widely used in the kernel and I personally prefer it. But I will change it if you want.

Quoted 9 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 187

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+		if (!(mcu->features & features[i].mask)) {  

Wouldn't be better

		if (mcu->features & features[i].mask)
			continue;

?

OK

Quoted 8 lines
Quoted 5 lines
(quoted diff) +1 -0
+			omnia_info_missing_feature(dev,

features[i].name);

drivers/platform/cznic/turris-omnia-mcu-base.c new file +3 -0
+			suggest_fw_upgrade = true;
+		}
+	}  

...

Quoted 6 lines
Quoted 3 lines
(quoted diff) +2 -0
+		dev_info(dev,
+			 "Consider upgrading MCU firmware with the

omnia-mcutool utility.\n");

You have so-o many dev_info() calls, are you sure you not abusing use of that?

I want the users to upgrade the MCU firmware. These infos are only printed during probe time, and won't be printed at all if the firmware is upgraded. Is this a problem?

Quoted 10 lines

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 217

Quoted 5 lines
(quoted diff) +2 -0
+	if (ret) {
+		dev_err(dev, "Cannot determine MCU supported

features: %d\n",

drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+			ret);
+		return ret;  
		return dev_err_probe(...);

Thx. Didn't know this one.

Quoted 12 lines
Quoted 1 line
(quoted diff) +1 -0
+	}  

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 223

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +4 -0
+	if (!client->irq) {
+		dev_err(dev, "IRQ resource not found\n");
+		return -ENODATA;
+	}  

Why you need to allocate memory, go through the initial checks, etc and then fail? What's wrong with checking this first?

Quoted 1 line

Why -ENODATA?!

OK

Quoted 4 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 229

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+static const struct of_device_id of_omnia_mcu_match[] = {
+	{ .compatible = "cznic,turris-omnia-mcu", },  

Inner comma is not needed.

OK

Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 230

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	{},  

No comma for the terminator entry.

OK

Quoted 8 lines
Quoted 1 line

+};

...

drivers/platform/cznic/turris-omnia-mcu-base.c line 234

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+static const struct i2c_device_id omnia_mcu_id[] = {
+	{ "turris-omnia-mcu", 0 },  

', 0' part is redundant.

OK

Quoted 15 lines
Quoted 2 lines
(quoted diff) +2 -0
+	{ }
+};  

...

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +9 -0
+static struct i2c_driver omnia_mcu_driver = {
+	.probe		= omnia_mcu_probe,
+	.id_table	= omnia_mcu_id,
+	.driver		= {
+		.name	= "turris-omnia-mcu",
+		.of_match_table = of_omnia_mcu_match,
+		.dev_groups = omnia_mcu_groups,
+	},
+};  
Quoted 3 lines
Quoted 1 line

+

Redundant blank line.

OK

Quoted 8 lines
Quoted 1 line

+module_i2c_driver(omnia_mcu_driver);

...

drivers/platform/cznic/turris-omnia-mcu.h line 9

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu.h new file +2 -0
+#ifndef __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H
+#define __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H  

WE_LIKE_VERY_LONG_AND_OVERFLOWED_DEFINITIONS_H!

Sigh... OK, I will change it :)

Quoted 3 lines
Quoted 1 line

+#include <asm/byteorder.h>

This usually goes after linux/*.h.

OK

Quoted 6 lines
Quoted 2 lines

+#include <linux/i2c.h>

Missing types.h, errno.h, ...

+ blank line?

Will change.

Quoted 9 lines
Quoted 1 line

+#include <linux/turris-omnia-mcu-interface.h>

...

drivers/platform/cznic/turris-omnia-mcu.h line 37

Quoted 3 lines
(quoted diff) +1 -0
+	ret = i2c_transfer(client->adapter, msgs,

ARRAY_SIZE(msgs));

drivers/platform/cznic/turris-omnia-mcu.h new file +1 -0
+	if (likely(ret == ARRAY_SIZE(msgs)))  

Why likely()? Please, justify.

Becuase it is unlikely the I2C transaction will fail. In most cases, it does not.

Quoted 10 lines

drivers/platform/cznic/turris-omnia-mcu.h line 42

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu.h new file +5 -0
+		return len;  
 
+	else if (ret < 0)
+		return ret;
+	else
+		return -EIO;  

In both cases the 'else' is redundant. Moreover, the usual pattern is to check for the errors first.

Quoted 8 lines
Quoted 1 line

+}

...

include/linux/turris-omnia-mcu-interface.h line 9

Quoted 2 lines
include/linux/turris-omnia-mcu-interface.h new file +2 -0
+#ifndef __INCLUDE_LINUX_TURRIS_OMNIA_MCU_INTERFACE_H
+#define __INCLUDE_LINUX_TURRIS_OMNIA_MCU_INTERFACE_H  

My gosh!

:)

Quoted 6 lines

include/linux/turris-omnia-mcu-interface.h line 13

Quoted 3 lines
include/linux/turris-omnia-mcu-interface.h new file +2 -0
+#include <linux/bits.h>  
 
+enum commands_e {  

Are you sure the name is unique enough / properly namespaced? Same Q to all enums.

I can change it. I wanted it to be compatible with the header in the firmware repository.

Quoted 7 lines

...

include/linux/turris-omnia-mcu-interface.h line 96

Quoted 1 line
include/linux/turris-omnia-mcu-interface.h new file +1 -0
+	/*CTL_RESERVED		= BIT(2),*/  

Missing spaces?
Also, needs a comment why this is commented out.

OK

Quoted 3 lines

include/linux/turris-omnia-mcu-interface.h line 101

Quoted 1 line
include/linux/turris-omnia-mcu-interface.h new file +1 -0
+	CTL_BOOTLOADER		= BIT(7)  

Add trailing comma.

OK

Marek

#6 lore raw

Re: [PATCH v2 2/7] platform: cznic: Add preliminary support for Turris Omnia MCU

Quoted 3 lines · Marek Behún
Quoted 1 line · On Tue, 19 Sep 2023 15:29:08 +0300 Andy Shevchenko <[email protected]> wrote:

On Tue, Sep 19, 2023 at 12:38:10PM +0200, Marek Behún wrote:

...

Quoted 9 lines
Quoted 6 lines
Quoted 4 lines

obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
+obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/

Why not ordered (to some extent) here (as you did in the other file)?

Where should I put it? The other entries are not ordered, see
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/Makefile?h=v6.6-rc2

With the visible context above, at least after chrome already much better, no?

...

Quoted 4 lines
Quoted 3 lines
Quoted 1 line

+turris-omnia-mcu-objs := turris-omnia-mcu-base.o

objs is for user space tools. Kernel code should use m,y.

Quoted 8 lines

I don't understand. There are plenty driver using -objs. The driver consists of multiple C source files. I thought this should be done as
obj-$(CONFIG_XXX) += xxx.o
xxx-objs := xxx-a.o xxx-b.o xxx-c.o

If you choose CONFIG_XXX = y, then the driver will be compiled into kernel, if you choose CONFIG_XXX = m, then the driver will be a module xxx.ko.

Hmm... Maybe I mixed something... Let me check.
For now than it's okay.

...

Quoted 8 lines
Quoted 4 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 9

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c new file +2 -0
+#include <linux/hex.h>
+#include <linux/module.h>  

Missing types.h, sysfs.h, mod_devicetable.h, i2c.h, ...

i2c.h comes from turris-omnia-mcu.h. The others I will fix. Does the the .c file also include all those headers if it includes a local header already?

For generic ones, yes.

...

Quoted 4 lines
Quoted 3 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	if (ret < 0)  

Can it return positive value? What would be the meaning of it?

Quoted 13 lines

Originally I had it return 0 on success, but recently I've had a discussion about this same thing with LED subsystem maintainer about some LED patches, Lee Jones, who requested the _read function to return number of bytes read on success. See at
https://lore.kernel.org/linux-leds/[email protected]/
where he says:
Read and write APIs, even abstracted ones such as these generally return the number of bytes successfully read and written respectively. If you are going to normalise, then please do so against this standard.
I do not agree with him, since this is a local command accessor which only succeeds if all the bytes are read/written as requested. But I adjusted my code as per his request since he is the maintainer.

This is strange. For example, regmap APIs never returns amount of data written or read. I think it's solely depends on the API. It might be useful for i²c APIs, in case you can do something about it. but if you have wrappers on top of that already (meaning not using directly the i2c_*() calls, I dunno the positive return is anyhow useful.

Quoted 4 lines

I will change the code back so that it returns 0 on success instead of the number of bytes written.

Quoted 1 line
Quoted 1 line
(quoted diff) +1 -0
+		return ret;  

...

Quoted 10 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 25

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	version[40] = '\0';  

How do you know the version has enough space?

The function is only use within the file, and both users know how much space it needs. But ok, I shall enforce it with the static keyword as
static int
omnia_get_version_hash(...,

			 u8 version[static 2*OMNIA_FW_VERSION_LEN + 1]);

Would that be okay?

Perhaps, it's a thing in a category "TIL", and it seems we have so few callers of that.

...

Quoted 10 lines
Quoted 5 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	int err;  

In two near functions you already inconsistent in the naming of the return code variable. Be consistent across all your code, i.e. choose one name and use it everywhere.

I use
ret - when the return value can also be positive and mean something err - when the return value is either 0 (no error) or negative errno
Is this inconsistent?

TBH sounds good, but I close to never had heard about such a distinction in a Linux kernel source file(s).

...

Quoted 7 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 154

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	int status;  

My gosh, it's a _third_ name for the same!

The variable holds value of the status register, which is needed at another point in this function. I thought naming it just "ret" would cause confusions... Do you really propose to rename it such?

It's not bad per se, just put yourself on the place of somebody who would like to understand the semantics of all these variables. Maybe you can add a comment on top explaining that (in short)?

...

Quoted 8 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 186

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	for (int i = 0; i < ARRAY_SIZE(features); ++i) {  

Why signed?

Because it is shorter and makes no difference and there are tons of
for (int i = 0; i < ARRAY_SIZE(x); i++)
in the kernel already. But if you really want, I can change it. Please let me know.

It just makes harder to get the code, because of possible unneeded questions like "if it's a signed type, maybe it's on purpose?"

Quoted 4 lines
Quoted 1 line

Why pre-increment?

Again, it makes no difference, is widely used in the kernel and I personally prefer it. But I will change it if you want.

Pre-increment usually also a sign of something special about the iterator variable. In non-special cases we use post-increment. And if you count the use of each you will see the difference.

...

Quoted 10 lines
Quoted 6 lines
Quoted 3 lines
(quoted diff) +2 -0
+		dev_info(dev,
+			 "Consider upgrading MCU firmware with the

omnia-mcutool utility.\n");

You have so-o many dev_info() calls, are you sure you not abusing use of that?

I want the users to upgrade the MCU firmware. These infos are only printed during probe time, and won't be printed at all if the firmware is upgraded. Is this a problem?

Depends how really useful they are. If you think it's way to go, go for it.

...

Quoted 6 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu.h line 37

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu.h new file +1 -0
+	if (likely(ret == ARRAY_SIZE(msgs)))  

Why likely()? Please, justify.

Becuase it is unlikely the I2C transaction will fail. In most cases, it does not.

Yes, but why likely() is needed? So, i.o.w. what's the benefit in _this_ case?

Quoted 1 line
Quoted 1 line
Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu.h new file +1 -0
+		return len;  

...

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

+enum commands_e {

Are you sure the name is unique enough / properly namespaced? Same Q to all enums.

I can change it. I wanted it to be compatible with the header in the firmware repository.

Are these being generated, or also statically written in that repo? If the latter, I think better to use more unique naming schema in the kernel.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#7 lore raw

Re: [PATCH v2 2/7] platform: cznic: Add preliminary support for Turris Omnia MCU

Hello Andy,

Quoted 12 lines · On Tue, 19 Sep 2023 21:27:04 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 3 lines · Marek Behún
Quoted 1 line · On Tue, 19 Sep 2023 15:29:08 +0300 Andy Shevchenko <[email protected]> wrote:

On Tue, Sep 19, 2023 at 12:38:10PM +0200, Marek Behún wrote:

...

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
+obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/

Quoted 6 lines
Quoted 4 lines
Quoted 1 line

Why not ordered (to some extent) here (as you did in the other file)?

Where should I put it? The other entries are not ordered, see
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/platform/Makefile?h=v6.6-rc2

With the visible context above, at least after chrome already much better, no?

OK

...

Quoted 7 lines
Quoted 5 lines
Quoted 1 line

Missing types.h, sysfs.h, mod_devicetable.h, i2c.h, ...

i2c.h comes from turris-omnia-mcu.h. The others I will fix. Does the the .c file also include all those headers if it includes a local header already?

For generic ones, yes.

OK

Quoted 18 lines
Quoted 4 lines
Quoted 3 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	if (ret < 0)    

Can it return positive value? What would be the meaning of it?

Quoted 13 lines

Originally I had it return 0 on success, but recently I've had a discussion about this same thing with LED subsystem maintainer about some LED patches, Lee Jones, who requested the _read function to return number of bytes read on success. See at
https://lore.kernel.org/linux-leds/[email protected]/
where he says:
Read and write APIs, even abstracted ones such as these generally return the number of bytes successfully read and written respectively. If you are going to normalise, then please do so against this standard.
I do not agree with him, since this is a local command accessor which only succeeds if all the bytes are read/written as requested. But I adjusted my code as per his request since he is the maintainer.

Quoted 5 lines

This is strange. For example, regmap APIs never returns amount of data written or read. I think it's solely depends on the API. It might be useful for i²c APIs, in case you can do something about it. but if you have wrappers on top of that already (meaning not using directly the i2c_*() calls, I dunno the positive return is anyhow useful.

I shall change it back then.

...

Quoted 11 lines
Quoted 7 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 154

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	int status;    

My gosh, it's a _third_ name for the same!

The variable holds value of the status register, which is needed at another point in this function. I thought naming it just "ret" would cause confusions... Do you really propose to rename it such?

It's not bad per se, just put yourself on the place of somebody who would like to understand the semantics of all these variables. Maybe you can add a comment on top explaining that (in short)?

OK

Quoted 11 lines
Quoted 8 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu-base.c line 186

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c new file +1 -0
+	for (int i = 0; i < ARRAY_SIZE(features); ++i) {    

Why signed?

Because it is shorter and makes no difference and there are tons of
for (int i = 0; i < ARRAY_SIZE(x); i++)
in the kernel already. But if you really want, I can change it. Please let me know.

It just makes harder to get the code, because of possible unneeded questions like "if it's a signed type, maybe it's on purpose?"

OK

Quoted 8 lines
Quoted 4 lines
Quoted 1 line

Why pre-increment?

Again, it makes no difference, is widely used in the kernel and I personally prefer it. But I will change it if you want.

Pre-increment usually also a sign of something special about the iterator variable. In non-special cases we use post-increment. And if you count the use of each you will see the difference.

OK

...

Quoted 8 lines
Quoted 6 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu.h line 37

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu.h new file +1 -0
+	if (likely(ret == ARRAY_SIZE(msgs)))    

Why likely()? Please, justify.

Becuase it is unlikely the I2C transaction will fail. In most cases, it does not.

Yes, but why likely() is needed? So, i.o.w. what's the benefit in _this_ case?

Compiler optimization (one branch avoided). But I guess this isn't a hot path, since I2C is insanely slow anyway. OK, I shall remove the likely() usage.

Quoted 7 lines

...

Quoted 5 lines
Quoted 4 lines
Quoted 1 line

+enum commands_e {

Are you sure the name is unique enough / properly namespaced? Same Q to all enums.

Quoted 6 lines
Quoted 2 lines

I can change it. I wanted it to be compatible with the header in the firmware repository.

Are these being generated, or also statically written in that repo? If the latter, I think better to use more unique naming schema in the kernel.

OK, I shall add some prefix.

Marek

#8 lore raw

Re: [PATCH v2 2/7] platform: cznic: Add preliminary support for Turris Omnia MCU

Quoted 6 lines · Marek Behún
Quoted 4 lines · On Tue, 19 Sep 2023 21:27:04 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 3 lines · Marek Behún
Quoted 1 line · On Tue, 19 Sep 2023 15:29:08 +0300 Andy Shevchenko <[email protected]> wrote:

On Tue, Sep 19, 2023 at 12:38:10PM +0200, Marek Behún wrote:

...

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines

drivers/platform/cznic/turris-omnia-mcu.h line 37

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu.h new file +1 -0
+	if (likely(ret == ARRAY_SIZE(msgs)))    

Why likely()? Please, justify.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Becuase it is unlikely the I2C transaction will fail. In most cases, it does not.

Yes, but why likely() is needed? So, i.o.w. what's the benefit in _this_ case?

Compiler optimization (one branch avoided). But I guess this isn't a hot path, since I2C is insanely slow anyway. OK, I shall remove the likely() usage.

Have you seen the difference in the generated code, btw?

I don't think it will get you one independently on the hot/slow path.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#9 lore raw

PATCH v2 3/7 platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

In reply to Marek Behún

Add support for GPIOs connected to the MCU on the Turris Omnia board.

This include:
- front button pin
- enable pins for USB regulators
- MiniPCIe / mSATA card presence pins in MiniPCIe port 0
- LED output pins from WAN ethernet PHY, LAN switch and MiniPCIe ports
- on board revisions 32+ also various peripheral resets and another voltage regulator enable pin

---
 .../sysfs-bus-i2c-devices-turris-omnia-mcu    |  16 +
 drivers/platform/cznic/Kconfig                |  15 +
 drivers/platform/cznic/Makefile               |   1 +
 .../platform/cznic/turris-omnia-mcu-base.c    |  12 +
 .../platform/cznic/turris-omnia-mcu-gpio.c    | 973 ++++++++++++++++++
 drivers/platform/cznic/turris-omnia-mcu.h     |  56 +
 6 files changed, 1073 insertions(+)
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-gpio.c
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +16 -0
index 69a4db7e20c0..c0efdf1b3803 100644
@@ -1,3 +1,19 @@
+What:		/sys/bus/i2c/devices/<mcu_device>/front_button_mode
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RW) The front button on the Turris Omnia router can be
+		configured either to change the intensity of all the LEDs on the
+		front panel, or to send the press event to the CPU as an
+		interrupt.
+
+		This file switches between these two modes:
+		- "mcu" makes the button press event be handled by the MCU to
+		  change the LEDs panel intensity.
+		- "cpu" makes the button press event be handled by the CPU.
+
+		Format: %s.
+
 What:		/sys/bus/i2c/devices/<mcu_device>/fw_features
 Date:		August 2023
 KernelVersion:	6.6
drivers/platform/cznic/Kconfig +15 -0
index f8560ff9c1af..3a8c3edcd7e6 100644
@@ -17,9 +17,24 @@ config TURRIS_OMNIA_MCU
 	tristate "Turris Omnia MCU driver"
 	depends on MACH_ARMADA_38X || COMPILE_TEST
 	depends on I2C
+	select GPIOLIB
+	select GPIOLIB_IRQCHIP
 	help
 	  Say Y here to add support for the features implemented by the
 	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
+	  The features include:
+	  - GPIO pins
+	    - to get front button press events (the front button can be
+	      configured either to generate press events to the CPU or to change
+	      front LEDs panel brightness)
+	    - to enable / disable USB port voltage regulators and to detect
+	      USB overcurrent
+	    - to detect MiniPCIe / mSATA card presence in MiniPCIe port 0
+	    - to configure resets of various peripherals on board revisions 32+
+	    - to enable / disable the VHV voltage regulator to the SOC in order
+	      to be able to program SOC's OTP on board revisions 32+
+	    - to get input from the LED output pins of the WAN ethernet PHY, LAN
+	      switch and MiniPCIe ports
 	  To compile this driver as a module, choose M here; the module will be
 	  called turris-omnia-mcu.
 
drivers/platform/cznic/Makefile +1 -0
index 4d0a9586538c..a6177f5b4fff 100644
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
 turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
+turris-omnia-mcu-objs		+= turris-omnia-mcu-gpio.o
drivers/platform/cznic/turris-omnia-mcu-base.c +12 -0
index 2d3c3f9c68fc..2954151468e5 100644
@@ -222,9 +222,20 @@ static int omnia_mcu_probe(struct i2c_client *client)
 		return -ENODATA;
 	}
 
+	ret = omnia_mcu_register_gpiochip(mcu);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
+static void omnia_mcu_remove(struct i2c_client *client)
+{
+	struct omnia_mcu *mcu = i2c_get_clientdata(client);
+
+	omnia_mcu_unregister_gpiochip(mcu);
+}
+
 static const struct of_device_id of_omnia_mcu_match[] = {
 	{ .compatible = "cznic,turris-omnia-mcu", },
 	{},
@@ -238,6 +249,7 @@ MODULE_DEVICE_TABLE(i2c, omnia_mcu_id);
 
 static struct i2c_driver omnia_mcu_driver = {
 	.probe		= omnia_mcu_probe,
+	.remove		= omnia_mcu_remove,
 	.id_table	= omnia_mcu_id,
 	.driver		= {
 		.name	= "turris-omnia-mcu",
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +973 -0

973 lines not on this page · raw message

drivers/platform/cznic/turris-omnia-mcu.h +56 -0

81 lines not on this page · raw message

Signature · 1 line
2.41.0
#10 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 3 lines · Marek Behún

Add support for GPIOs connected to the MCU on the Turris Omnia board.

This include:

includes

Quoted 6 lines

- front button pin
- enable pins for USB regulators
- MiniPCIe / mSATA card presence pins in MiniPCIe port 0
- LED output pins from WAN ethernet PHY, LAN switch and MiniPCIe ports
- on board revisions 32+ also various peripheral resets and another voltage regulator enable pin

Can the main driver provide a regmap and all other use it?

...

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu line 3

Quoted 2 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +2 -0
+Date:		August 2023
+KernelVersion:	6.6

Same as per previous patch review.

...

drivers/platform/cznic/turris-omnia-mcu-base.c static int omnia_mcu_probe(struct i2c_client *client) · line 229

Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+	ret = omnia_mcu_register_gpiochip(mcu);
+	if (ret)
+		return ret;
+
 	return 0;
	return ..._gpiochip(...);

?

...

Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+	switch (cmd) {
+	case CMD_GENERAL_CONTROL:
+		buf[1] = val;
+		buf[2] = mask;
+		len = 3;

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 199

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +10 -0
+		break;
+
+	case CMD_EXT_CONTROL:
+		put_unaligned_le16(val, &buf[1]);
+		put_unaligned_le16(mask, &buf[3]);
+		len = 5;
+		break;
+
+	default:
+		unreachable();

You meant BUG_ON()?

Quoted 1 line
(quoted diff) +1 -0
+	}

...

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +10 -0
+static int omnia_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct omnia_mcu *mcu = gpiochip_get_data(gc);
+
+	if (offset == OMNIA_GPIO_PHY_SFP_OFFSET) {
+		int val;
+
+		mutex_lock(&mcu->lock);
+		val = omnia_cmd_read_bit(mcu->client,
+					 CMD_GET_EXT_CONTROL_STATUS,

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 242

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +9 -0
+					 EXT_CTL_PHY_SFP_AUTO);
+		mutex_unlock(&mcu->lock);
+
+		if (val < 0)
+			return val;
+
+		if (val)
+			return GPIO_LINE_DIRECTION_IN;
 
+		else

Redundant 'else'.

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 248

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+			return GPIO_LINE_DIRECTION_OUT;
+	}
+
+	if (omnia_gpios[offset].ctl_cmd)
+		return GPIO_LINE_DIRECTION_OUT;
 
+	else

Ditto.

Quoted 2 lines
(quoted diff) +2 -0
+		return GPIO_LINE_DIRECTION_IN;
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 262

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+	if (offset == OMNIA_GPIO_PHY_SFP_OFFSET)
+		return omnia_ctl_cmd(mcu, CMD_EXT_CONTROL, EXT_CTL_PHY_SFP_AUTO,
+				     EXT_CTL_PHY_SFP_AUTO);
+
+	if (gpio->ctl_cmd)
+		return -EOPNOTSUPP;

I believe internally we use ENOTSUPP.
Ditto for all cases like this.

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 272

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	uint16_t val, mask;

So, why uintXX_t out of a sudden?

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 282

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +3 -0
+	if (gpio->ctl_cmd)
+		return omnia_ctl_cmd(mcu, gpio->ctl_cmd, val, mask);
+	else

Redundant 'else'.

Quoted 2 lines
(quoted diff) +2 -0
+		return -EOPNOTSUPP;
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 392

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+static void omnia_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	const struct omnia_gpio *gpio = &omnia_gpios[offset];
+	struct omnia_mcu *mcu = gpiochip_get_data(gc);
+	u16 val, mask;
 
+	int err = 0;

Redundant assignment.

Quoted 11 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +11 -0
+	if (!gpio->ctl_cmd)
+		return;
+
+	mask = gpio->ctl_bit;
+	val = value ? mask : 0;
+
+	err = omnia_ctl_cmd(mcu, gpio->ctl_cmd, val, mask);
+	if (err)
+		dev_err(&mcu->client->dev, "Cannot set GPIO %u: %d\n",
+			offset, err);
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 439

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+	mutex_lock(&mcu->lock);
+
+	if (ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_GENERAL_CONTROL, ctl,
+					     ctl_mask);
 
+	if (!err && ext_ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_EXT_CONTROL, ext_ctl,
+					     ext_ctl_mask);

Can it be

	if (err)
		goto out_unlock;

	if (_mask)
		...

?

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 444

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +3 -0
+	mutex_unlock(&mcu->lock);
 
+	if (err)
+		dev_err(&mcu->client->dev, "Cannot set GPIOs: %d\n", err);

How is useful this one?

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 454

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+static bool omnia_gpio_available(struct omnia_mcu *mcu,
+				 const struct omnia_gpio *gpio)
+{
+	if (gpio->feat_mask)
+		return (mcu->features & gpio->feat_mask) == gpio->feat;
+	else if (gpio->feat)
+		return mcu->features & gpio->feat;
+	else

Redundant 'else':s.

Quoted 2 lines
(quoted diff) +2 -0
+		return true;
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 464

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+static int omnia_gpio_init_valid_mask(struct gpio_chip *gc,
+				      unsigned long *valid_mask,
+				      unsigned int ngpios)
+{
+	struct omnia_mcu *mcu = gpiochip_get_data(gc);
 
+	bitmap_zero(valid_mask, ngpios);

No need.

Also do you have bitops.h included?

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 470

Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+	for (int i = 0; i < ngpios; ++i) {
+		const struct omnia_gpio *gpio = &omnia_gpios[i];
+
+		if (!gpio->cmd && !gpio->int_bit)
+			continue;

Use clear_bit() here...

Review

Quoted 2 lines
(quoted diff) +2 -0
+		if (omnia_gpio_available(mcu, gpio))
+			set_bit(i, valid_mask);

...and assign_bit() here.

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	}
+
+	return 0;
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 582

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +3 -0
+	dev_dbg(&mcu->client->dev,
+		"set int mask %02x %02x %02x %02x %02x %02x %02x %02x\n",
+		cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8]);

%8ph

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 594

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +7 -0
+	/*
+	 * Remember which GPIOs have both rising and falling interrupts enabled.
+	 * For those we will cache their value so that .get() method is faster.
+	 * We also need to forget cached values of GPIOs that aren't cached
+	 * anymore.
+	 */
+	if (!err) {
	if (err)
		goto out_unlock;
Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+		mcu->both = rising & falling;
+		mcu->is_cached &= mcu->both;
+	}
+
+	mutex_unlock(&mcu->lock);

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 620

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+static void omnia_irq_init_valid_mask(struct gpio_chip *gc,
+				      unsigned long *valid_mask,
+				      unsigned int ngpios)
+{
+	struct omnia_mcu *mcu = gpiochip_get_data(gc);
 
+	bitmap_zero(valid_mask, ngpios);

No need (see above how).

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +10 -0
+	for (int i = 0; i < ngpios; ++i) {
+		const struct omnia_gpio *gpio = &omnia_gpios[i];
+
+		if (!gpio->int_bit)
+			continue;
+
+		if (omnia_gpio_available(mcu, gpio))
+			set_bit(i, valid_mask);
+	}
+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 636

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	u8 cmd[9] = {};

Magic number in a few places. Please, use self-explanatory pre-defined constant instead.

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 666

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +2 -0
+		dev_err(&mcu->client->dev, "Cannot read pending IRQs: %d\n",
+			err);

In all functions with help of

	struct device *dev = &mcu->client->dev;

you can make code shorter.

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);

With a help of two masks, you can access to the both edges as to 64-bit value and simplify the code.

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 691

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	int ret = omnia_cmd_read_u16(mcu->client, CMD_GET_STATUS_WORD);

Please, split this for better maintenance.

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +2 -0
+	if (ret < 0)
+		return ret;

...

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+static irqreturn_t omnia_irq_thread_handler(int irq, void *dev_id)
+{
+	struct omnia_mcu *mcu = dev_id;
+	irqreturn_t res = IRQ_NONE;

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 822

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +10 -0
+	unsigned long pending;
+	int i;
+
+	if (!omnia_irq_read_pending(mcu, &pending))
+		return IRQ_NONE;
+
+	for_each_set_bit(i, &pending, 32) {
+		unsigned int nested_irq =
+			irq_find_mapping(mcu->gc.irq.domain,
+					 omnia_int_to_gpio_idx[i]);

It's much better to read in a form

		unsigned int nested_irq;
		domain = ...

		nested_irq = irq_find_mapping(domain, omnia_int_to_gpio_idx[i]);

(exactly 80 characters, btw).

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 826

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +2 -0
+		handle_nested_irq(nested_irq);
 
+		res = IRQ_HANDLED;

No need.

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 829

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +2 -0
+	}
 
+	return res;
	return IRQ_RETVAL(pending);
Quoted 1 line

+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 844

Quoted 13 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +12 -0
+static ssize_t front_button_mode_show(struct device *dev,
+				      struct device_attribute *a, char *buf)
+{
+	struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev));
+	int val;
+
+	if (mcu->features & FEAT_NEW_INT_API)
+		val = omnia_cmd_read_bit(mcu->client, CMD_GET_STATUS_WORD,
+					 STS_BUTTON_MODE);
+	else
+		val = !!(mcu->last_status & STS_BUTTON_MODE);
 
+	if (val < 0)

Can be true only in one case, why to check for the second oner?/

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 847

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +2 -0
+		return val;
 
+	return sysfs_emit(buf, "%s\n", val ? "cpu" : "mcu");

With a static const array of string literals...

Quoted 1 line

+}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 865

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+	if (sysfs_streq(buf, "mcu"))
+		val = 0;
+	else if (sysfs_streq(buf, "cpu"))
+		val = mask;
+	else
+		return -EINVAL;

...and help of sysfs_match_string() you can simplify the code.

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 882

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+static struct attribute *omnia_mcu_gpio_attrs[] = {
+	&dev_attr_front_button_mode.attr,
+	NULL,
+};
+
+static const struct attribute_group omnia_mcu_gpio_group = {
+	.attrs = omnia_mcu_gpio_attrs,
+};

ATTRIBUTE_GROUPS() ?

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 925

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	err = devm_gpiochip_add_data(dev, &mcu->gc, mcu);
+	if (err) {
+		dev_err(dev, "Cannot add GPIO chip: %d\n", err);
+		return err;
		return dev_err_probe(...);

Ditto for other places like this in the probe stage.

Quoted 1 line
(quoted diff) +1 -0
+	}

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 959

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	err = devm_device_add_group(dev, &omnia_mcu_gpio_group);

No way, no-one should use the API scheduled for removal. What's wrong with .dev_groups ?

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu)
+{
+	if (!(mcu->features & FEAT_NEW_INT_API))
+		cancel_delayed_work_sync(&mcu->button_release_emul_work);
+
+	mutex_destroy(&mcu->lock);

Wrong order?

Quoted 1 line

+}

...

drivers/platform/cznic/turris-omnia-mcu.h line 25

Quoted 7 lines

struct omnia_mcu {

drivers/platform/cznic/turris-omnia-mcu.h +3 -0
 	struct i2c_client *client;
 	const char *type;
 	u16 features;
+
+	/* GPIO chip */
+	struct gpio_chip gc;

Making this a first member may lead to the better code. Check with bloat-o-meter.

drivers/platform/cznic/turris-omnia-mcu.h line 30

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu.h +5 -0
+	struct mutex lock;
+	u32 mask, rising, falling, both, cached, is_cached;
 
+	/* Old MCU firmware handling needs the following */
+	u16 last_status;
+	struct delayed_work button_release_emul_work;

Swapping these two may free a few bytes. Check with pahole tool.

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu.h +1 -0
+	bool button_pressed_emul;
 };

...

drivers/platform/cznic/turris-omnia-mcu.h static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, void * · line 78

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu.h +7 -0
+	if (!bits) {
+		*reply = 0;
+		return 0;
+	}
+
+	ret = omnia_cmd_read(client, cmd, reply, (ilog2(bits) >> 3) + 1);
 
+

Redundant blank line.

drivers/platform/cznic/turris-omnia-mcu.h static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, void * · line 80

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu.h +2 -0
+	if (ret >= 0)
 
+		*reply = le32_to_cpu(*reply) & bits;

Huh?! Please, check your code with sparse like

	make W=1 C=1 CF=-D__CHECK_ENDIAN__ ...
Quoted 1 line
(quoted diff) +1 -0
+	return ret < 0 ? ret : 0;
Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#11 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Hi Andy,

I'm sending reply to some of your comments. For those to which I do not reply I will simply incorporate your suggestions.

Quoted 9 lines · On Tue, 19 Sep 2023 16:00:39 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 6 lines · Marek Behún

- front button pin
- enable pins for USB regulators
- MiniPCIe / mSATA card presence pins in MiniPCIe port 0
- LED output pins from WAN ethernet PHY, LAN switch and MiniPCIe ports
- on board revisions 32+ also various peripheral resets and another voltage regulator enable pin

Can the main driver provide a regmap and all other use it?

It can't. These are not registers accessible via I2C at specific addresses, but commands with different-length arguments. This was already discussed 4 years ago with Jacek, see
https://www.spinics.net/lists/linux-leds/msg11587.html
There, Jacek suggested writing regmap API for the leds-turris-omnia driver (the MCU is also a LED controller and has same command interface, only at different I2C address).

Quoted 5 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+	if (gpio->ctl_cmd)
+		return -EOPNOTSUPP;  

I believe internally we use ENOTSUPP.
Ditto for all cases like this.

checkpatch warns:
ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP

Quoted 14 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 439

Quoted 11 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+	mutex_lock(&mcu->lock);
+
+	if (ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu,
  CMD_GENERAL_CONTROL, ctl,
+					     ctl_mask);  
 
+	if (!err && ext_ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_EXT_CONTROL,
  ext_ctl,
+					     ext_ctl_mask);  

Can it be

Quoted 5 lines
	if (err)
		goto out_unlock;

	if (_mask)
		...

Linus suggested using guards, so I will refactor this away.

Quoted 7 lines

Review

Quoted 5 lines
(quoted diff) +3 -0
+	mutex_unlock(&mcu->lock);  
 
+	if (err)
+		dev_err(&mcu->client->dev, "Cannot set GPIOs:
  %d\n", err);  

How is useful this one?

The function does not return, this way we will know something has gone wrong. I am used to do this from the networking subsystem, where people at least from mv88e6xxx wanted such behavior. Do you want me to drop this?

Quoted 11 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 464

Quoted 7 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +6 -0
+static int omnia_gpio_init_valid_mask(struct gpio_chip *gc,
+				      unsigned long *valid_mask,
+				      unsigned int ngpios)
+{
+	struct omnia_mcu *mcu = gpiochip_get_data(gc);  
 
+	bitmap_zero(valid_mask, ngpios);  

No need.

Also do you have bitops.h included?

bitmap.h actually for this

Quoted 7 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);  

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

Quoted 10 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 882

Quoted 8 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+static struct attribute *omnia_mcu_gpio_attrs[] = {
+	&dev_attr_front_button_mode.attr,
+	NULL,
+};
+
+static const struct attribute_group omnia_mcu_gpio_group = {
+	.attrs = omnia_mcu_gpio_attrs,
+};  

ATTRIBUTE_GROUPS() ?

I only want to create ane attribute_group. (I am using devm_device_add_group(), not devm_device_add_groups()).

Quoted 4 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 959

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	err = devm_device_add_group(dev, &omnia_mcu_gpio_group);  

No way, no-one should use the API scheduled for removal. What's wrong with .dev_groups ?

Can I add them conditionally? GPIO chip is always created, but the patch 4/7 only adds the attributes conditionally. Is it possible via .dev_groups?

Quoted 11 lines

...

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu)
+{
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);  

Wrong order?

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

Quoted 10 lines

drivers/platform/cznic/turris-omnia-mcu.h line 25

Quoted 7 lines

struct omnia_mcu {

drivers/platform/cznic/turris-omnia-mcu.h +3 -0
 	struct i2c_client *client;
 	const char *type;
 	u16 features;
+
+	/* GPIO chip */
+	struct gpio_chip gc;  

Making this a first member may lead to the better code. Check with
bloat-o-meter.

kabel@dellmb ~/linux $ ./scripts/bloat-o-meter \
turris-omnia-mcu.o turris-omnia-mcu-gpiochip-first.o
add/remove: 0/0 grow/shrink: 9/0 up/down: 52/0 (52)

Function                                     old     new   delta
omnia_mcu_register_gpiochip                  840     852     +12
omnia_mcu_probe                              696     708     +12
omnia_mcu_unregister_gpiochip                 20      24      +4
omnia_irq_bus_sync_unlock                    256     260      +4
omnia_irq_bus_lock                            36      40      +4
omnia_gpio_get_multiple                      872     876      +4
omnia_gpio_get                               372     376      +4
fw_features_show                              28      32      +4
front_button_mode_show                       260     264      +4

Total: Before=10468, After=10520, chg +0.50%

Seems the code grew when I swapped it.

Marek

#12 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 3 lines · Marek Behún
Quoted 1 line · On Tue, 19 Sep 2023 16:00:39 +0300 Andy Shevchenko <[email protected]> wrote:

On Tue, Sep 19, 2023 at 12:38:11PM +0200, Marek Behún wrote:

...

Quoted 4 lines
Quoted 1 line

Ditto for all cases like this.

checkpatch warns:
ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP

So, fix checkpatch then. It has false positives, because it doesn't know if the actual error code will sneak outside the kernel or not. But in the subsystem we internally use ENOTSUPP.

...

Quoted 1 line

Linus suggested using guards, so I will refactor this away.

Even better.

...

Quoted 9 lines
Quoted 4 lines

Review

Quoted 2 lines
(quoted diff) +1 -0
+		dev_err(&mcu->client->dev, "Cannot set GPIOs:
  %d\n", err);  

How is useful this one?

The function does not return, this way we will know something has gone wrong. I am used to do this from the networking subsystem, where people at least from mv88e6xxx wanted such behavior. Do you want me to drop this?

You are the developer and owner of the specific hardware, if you have a good justification for this message _in production_, then leave it, otherwise drop.

...

Quoted 7 lines
Quoted 5 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	bitmap_zero(valid_mask, ngpios);  

No need.

Also do you have bitops.h included?

bitmap.h actually for this

Right, but I already thought a step forward, when you drop bitmap APIs, the bitops are still in place.

...

Quoted 17 lines
Quoted 7 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);  

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Quoted 4 lines

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

If you use proper bitmaps, perhaps this will be easier. You can use one for each and merge them whenever you want (with bitmap_or() call) or split (with bitmap_and() respectively):

bitmap_or(full, raising, failing); // merge bitmap_and(raising, full, rasing_mask); // split

...

Quoted 4 lines
Quoted 1 line

ATTRIBUTE_GROUPS() ?

I only want to create ane attribute_group. (I am using devm_device_add_group(), not devm_device_add_groups()).

...and you should not use that APIs.

Quoted 8 lines
Quoted 4 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 959

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +1 -0
+	err = devm_device_add_group(dev, &omnia_mcu_gpio_group);  

No way, no-one should use the API scheduled for removal. What's wrong with .dev_groups ?

Can I add them conditionally? GPIO chip is always created, but the patch 4/7 only adds the attributes conditionally. Is it possible via .dev_groups?

Yes. You use __ATTRIBUTE_GROUPS() and .is_visible callback.

...

Quoted 11 lines
Quoted 8 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu)
+{
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);  

Wrong order?

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

I mean you are using a lot of devm(), can mutex be used in IRQ or whatever that can be triggered after this call?

...

Quoted 23 lines
Quoted 9 lines

drivers/platform/cznic/turris-omnia-mcu.h line 25

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu.h +3 -0
 	struct i2c_client *client;
 	const char *type;
 	u16 features;
+
+	/* GPIO chip */
+	struct gpio_chip gc;  

Making this a first member may lead to the better code. Check with
bloat-o-meter.

kabel@dellmb ~/linux $ ./scripts/bloat-o-meter \
turris-omnia-mcu.o turris-omnia-mcu-gpiochip-first.o
add/remove: 0/0 grow/shrink: 9/0 up/down: 52/0 (52)

Function                                     old     new   delta
omnia_mcu_register_gpiochip                  840     852     +12
omnia_mcu_probe                              696     708     +12
omnia_mcu_unregister_gpiochip                 20      24      +4
omnia_irq_bus_sync_unlock                    256     260      +4
omnia_irq_bus_lock                            36      40      +4
omnia_gpio_get_multiple                      872     876      +4
omnia_gpio_get                               372     376      +4
fw_features_show                              28      32      +4
front_button_mode_show                       260     264      +4
Quoted 3 lines

Total: Before=10468, After=10520, chg +0.50%

Seems the code grew when I swapped it.

Thanks for checking! It means that access to client pointer is needed more often.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#13 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Hello Andy,

Quoted 6 lines · On Thu, 21 Sep 2023 12:55:08 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 2 lines

checkpatch warns:
ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP

So, fix checkpatch then. It has false positives, because it doesn't know if the actual error code will sneak outside the kernel or not. But in the subsystem we internally use ENOTSUPP.

I will change it, sorry, checkpatch warning confused me.

Quoted 22 lines
Quoted 17 lines
Quoted 7 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);    

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Quoted 4 lines

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

Quoted 6 lines

If you use proper bitmaps, perhaps this will be easier. You can use one for each and merge them whenever you want (with bitmap_or() call) or split (with bitmap_and() respectively):

bitmap_or(full, raising, failing); // merge bitmap_and(raising, full, rasing_mask); // split

Hmm. But then what? I or the result and use it as pending interrupt bitmap, to be iterated over. The indexes of the bits correspond to the constants in the MCU API.

So after your suggestion I have rising and falling containgin
rising = 00rr00rr00rr00rr; /* r means rising bits */ falling = 00ff00ff00ff00ff; /* f means falling bits */ pending = rising | falling;
which means:
pending = pp00pp00pp00pp; /* p means pending bits */
But these bit positions do not correspond to the interrupt number anymore.

I still think the de-interleaving of the buffer from
rr ff rr ff rr ff rr ff
into two words:
rising = rrrrrrrr;
falling = ffffffff;
is simpler...

Or am I wrong?

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +5 -0
+void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu)
+{
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);    

Wrong order?

Quoted 5 lines
Quoted 2 lines

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

I mean you are using a lot of devm(), can mutex be used in IRQ or whatever that can be triggered after this call?

OK, I think I need to free the irq before canceling the work. Thank you!

Marek

#14 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 2 lines · Marek Behún

On Thu, 21 Sep 2023 12:55:08 +0300
Andy Shevchenko <[email protected]> wrote:

...

Quoted 42 lines
Quoted 22 lines
Quoted 17 lines
Quoted 7 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);    

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Quoted 4 lines

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

Quoted 6 lines

If you use proper bitmaps, perhaps this will be easier. You can use one for each and merge them whenever you want (with bitmap_or() call) or split (with bitmap_and() respectively):

bitmap_or(full, raising, failing); // merge bitmap_and(raising, full, rasing_mask); // split

Hmm. But then what? I or the result and use it as pending interrupt bitmap, to be iterated over. The indexes of the bits correspond to the constants in the MCU API.

So after your suggestion I have rising and falling containgin
rising = 00rr00rr00rr00rr; /* r means rising bits */ falling = 00ff00ff00ff00ff; /* f means falling bits */ pending = rising | falling;
which means:
pending = pp00pp00pp00pp; /* p means pending bits */
But these bit positions do not correspond to the interrupt number anymore.

Quoted 6 lines

I still think the de-interleaving of the buffer from
rr ff rr ff rr ff rr ff
into two words:
rising = rrrrrrrr;
falling = ffffffff;
is simpler...

There are two sides of this: OS and hardware. See Xilinx GPIO driver how it's made there. But before going that way, check on
https://lore.kernel.org/all/[email protected]/
That APIs you would need I am pretty sure.

...

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +3 -0
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);    

Wrong order?

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

I mean you are using a lot of devm(), can mutex be used in IRQ or whatever that can be triggered after this call?

OK, I think I need to free the irq before canceling the work. Thank you!

Can you rather switch everything to be devm managed?

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#15 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 55 lines · On Fri, 22 Sep 2023 17:18:56 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 2 lines · Marek Behún

On Thu, 21 Sep 2023 12:55:08 +0300
Andy Shevchenko <[email protected]> wrote:

...

Quoted 42 lines
Quoted 22 lines
Quoted 17 lines
Quoted 7 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 673

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);      

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Quoted 4 lines

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

Quoted 6 lines

If you use proper bitmaps, perhaps this will be easier. You can use one for each and merge them whenever you want (with bitmap_or() call) or split (with bitmap_and() respectively):

bitmap_or(full, raising, failing); // merge bitmap_and(raising, full, rasing_mask); // split

Hmm. But then what? I or the result and use it as pending interrupt bitmap, to be iterated over. The indexes of the bits correspond to the constants in the MCU API.

So after your suggestion I have rising and falling containgin
rising = 00rr00rr00rr00rr; /* r means rising bits */ falling = 00ff00ff00ff00ff; /* f means falling bits */ pending = rising | falling;
which means:
pending = pp00pp00pp00pp; /* p means pending bits */
But these bit positions do not correspond to the interrupt number anymore.

Quoted 6 lines

I still think the de-interleaving of the buffer from
rr ff rr ff rr ff rr ff
into two words:
rising = rrrrrrrr;
falling = ffffffff;
is simpler...

Quoted 4 lines

There are two sides of this: OS and hardware. See Xilinx GPIO driver how it's made there. But before going that way, check on
https://lore.kernel.org/all/[email protected]/
That APIs you would need I am pretty sure.

Andy, thank you for patience in reviewing this.

Hmm. I like the names, scatter and gather. In the firmware, I used interleave and deinterleave, see
https://gitlab.nic.cz/turris/hw/omnia_hw_ctrl/-/blob/master/src/drivers/i2c_iface.c#L360

But those functions work bit-wise. I realize that the I2C transfers in the driver are so slow that such bit-wise cycling over a bitmap won't matter much, but I still find my original proposal more simple and straight-forward. But I will cave if you insist. Please let me know (and can I then send your local patch in the series?)

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
Quoted 6 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 972

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +3 -0
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);      

Wrong order?

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

Quoted 6 lines
Quoted 4 lines
Quoted 2 lines

I mean you are using a lot of devm(), can mutex be used in IRQ or whatever that can be triggered after this call?

OK, I think I need to free the irq before canceling the work. Thank you!

Can you rather switch everything to be devm managed?

There are no devm_ calls for mutex and work initialization. Are you suggesting that I should write a release function for the gpio sub-driver? Something like

static void omnia_gpiochip_release(dev, res)
{
cancel_work();
mutex_destroy();
}

int omnia_mcu_register_gpiochip(mcu)
{
...
x = devres_alloc(omnia_gpiochip_release);
devres_add(dev, x);
...
}

Marek

#16 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 5 lines · Marek Behún
Quoted 3 lines · On Fri, 22 Sep 2023 17:18:56 +0300 Andy Shevchenko <[email protected]> wrote:
Quoted 2 lines · Marek Behún

On Thu, 21 Sep 2023 12:55:08 +0300
Andy Shevchenko <[email protected]> wrote:

...

Quoted 60 lines
Quoted 49 lines
Quoted 42 lines
Quoted 22 lines
Quoted 17 lines
Quoted 7 lines

Review

Quoted 4 lines
(quoted diff) +4 -0
+	rising = reply[0] | (reply[2] << 8) | (reply[4] << 16) |
+		 (reply[6] << 24);
+	falling = reply[1] | (reply[3] << 8) | (reply[5] << 16) |
+		  (reply[7] << 24);      

With a help of two masks, you can access to the both edges as to
64-bit value and simplify the code.

Huh? As in
rising = reply & 0x00ff00ff00ff00ff;
falling = reply & 0xff00ff00ff00ff00;
?
But then I can't or the rising bit with the corresponding falling bit to get pending...
Or I guess i can with:
pending = rising & (pending >> 8);

Quoted 4 lines

Am I understanding you correctly?

But then I would need to store the mask in driver data as a 64-bit value with half the data not used. Also the CPU is 32-bit.

Quoted 6 lines

If you use proper bitmaps, perhaps this will be easier. You can use one for each and merge them whenever you want (with bitmap_or() call) or split (with bitmap_and() respectively):

bitmap_or(full, raising, failing); // merge bitmap_and(raising, full, rasing_mask); // split

Hmm. But then what? I or the result and use it as pending interrupt bitmap, to be iterated over. The indexes of the bits correspond to the constants in the MCU API.

So after your suggestion I have rising and falling containgin
rising = 00rr00rr00rr00rr; /* r means rising bits */ falling = 00ff00ff00ff00ff; /* f means falling bits */ pending = rising | falling;
which means:
pending = pp00pp00pp00pp; /* p means pending bits */
But these bit positions do not correspond to the interrupt number anymore.

Quoted 6 lines

I still think the de-interleaving of the buffer from
rr ff rr ff rr ff rr ff
into two words:
rising = rrrrrrrr;
falling = ffffffff;
is simpler...

Quoted 4 lines

There are two sides of this: OS and hardware. See Xilinx GPIO driver how it's made there. But before going that way, check on
https://lore.kernel.org/all/[email protected]/
That APIs you would need I am pretty sure.

Andy, thank you for patience in reviewing this.

Hmm. I like the names, scatter and gather. In the firmware, I used interleave and deinterleave, see
https://gitlab.nic.cz/turris/hw/omnia_hw_ctrl/-/blob/master/src/drivers/i2c_iface.c#L360

Quoted 5 lines

But those functions work bit-wise. I realize that the I2C transfers in the driver are so slow that such bit-wise cycling over a bitmap won't matter much, but I still find my original proposal more simple and straight-forward. But I will cave if you insist. Please let me know (and can I then send your local patch in the series?)

You can. but I need to add test cases there.

Yes, I think the best is to have hardware values and Linux cached ones to be separated. Let me try my best and send it out this week.

...

Quoted 21 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
Quoted 6 lines

Review

Quoted 4 lines
(quoted diff) +3 -0
+	if (!(mcu->features & FEAT_NEW_INT_API))
+
  cancel_delayed_work_sync(&mcu->button_release_emul_work); +
+	mutex_destroy(&mcu->lock);      

Wrong order?

No, the mutex may be used in the work. Can't destroy it first. Or am I misunderstanding something?

Quoted 6 lines
Quoted 4 lines
Quoted 2 lines

I mean you are using a lot of devm(), can mutex be used in IRQ or whatever that can be triggered after this call?

OK, I think I need to free the irq before canceling the work. Thank you!

Can you rather switch everything to be devm managed?

There are no devm_ calls for mutex and work initialization. Are you suggesting that I should write a release function for the gpio sub-driver? Something like

Quoted 5 lines

static void omnia_gpiochip_release(dev, res)
{
cancel_work();
mutex_destroy();
}

Not together, but
- for mutex use devm_add_action_or_reset() as done in many other drivers for the same reason;
- for the work we have devm_work_autocancel()
(you need to include devm-helpers.h)

Quoted 7 lines

int omnia_mcu_register_gpiochip(mcu)
{
...
x = devres_alloc(omnia_gpiochip_release);
devres_add(dev, x);
...
}

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#17 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

In reply to Andy Shevchenko
Quoted 12 lines · On Tue, 19 Sep 2023 16:00:39 +0300 Andy Shevchenko <[email protected]> wrote:

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 439

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+	mutex_lock(&mcu->lock);
+
+	if (ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_GENERAL_CONTROL, ctl,
+					     ctl_mask);  
 
+	if (!err && ext_ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_EXT_CONTROL, ext_ctl,
+					     ext_ctl_mask);  

Can it be

Quoted 7 lines
	if (err)
		goto out_unlock;

	if (_mask)
		...

?

Hi Andy,

so I am refactoring this to use guard(mutex), but now I have this:

	guard(mutex, &mcu->lock);

	if (ctl_mask) {
		err = ...;
		if (err)
			goto out_err;
	}

	if (ext_ctl_mask) {
		err = ...;
		if (err)
			goto out_err;
	}

	return;
out_err:
	dev_err(dev, "Cannot set GPIOs: %d\n", err);

which clearly is not any better... or at least the original
if (!err && ext_ctl_mask)
is better IMO.

Compare with:

	guard(mutex, &mcu->lock);

	if (ctl_mask)
		err = ...;

	if (!err && ext_ctl_mask)
		err = ...;

	if (err)
		dev_err(dev, "Cannot set GPIOs: %d\n", err);

Do you have a better suggestion?

#18 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 2 lines · Marek Behún

On Tue, 19 Sep 2023 16:00:39 +0300
Andy Shevchenko <[email protected]> wrote:

...

Quoted 42 lines
Quoted 12 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 439

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +8 -0
+	mutex_lock(&mcu->lock);
+
+	if (ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_GENERAL_CONTROL, ctl,
+					     ctl_mask);  
 
+	if (!err && ext_ctl_mask)
+		err = omnia_ctl_cmd_unlocked(mcu, CMD_EXT_CONTROL, ext_ctl,
+					     ext_ctl_mask);  

Can it be

Quoted 7 lines
	if (err)
		goto out_unlock;

	if (_mask)
		...

?

Hi Andy,

so I am refactoring this to use guard(mutex), but now I have this:

	guard(mutex, &mcu->lock);

	if (ctl_mask) {
		err = ...;
		if (err)
			goto out_err;
	}

	if (ext_ctl_mask) {
		err = ...;
		if (err)
			goto out_err;
	}

	return;
out_err:
	dev_err(dev, "Cannot set GPIOs: %d\n", err);
Quoted 1 line

which clearly is not any better... or at least the original

...which rather means that the design of above is not so good, i.e. why do you need the same message in the different situations?

Quoted 2 lines

if (!err && ext_ctl_mask)
is better IMO.

I disagree.

Quoted 14 lines

Compare with:

	guard(mutex, &mcu->lock);

	if (ctl_mask)
		err = ...;

	if (!err && ext_ctl_mask)
		err = ...;

	if (err)
		dev_err(dev, "Cannot set GPIOs: %d\n", err);
Quoted 1 line

Do you have a better suggestion?

Use different messages (if even needed) for different situations.

With cleanup.h in place you shouldn't supposed to have goto:s (in simple cases like yours).

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#19 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

In reply to Marek Behún

Hi Marek,

thanks for your patch! This is a very interesting hardware.

Quoted 11 lines · Marek Behún

Add support for GPIOs connected to the MCU on the Turris Omnia board.

This include:
- front button pin
- enable pins for USB regulators
- MiniPCIe / mSATA card presence pins in MiniPCIe port 0
- LED output pins from WAN ethernet PHY, LAN switch and MiniPCIe ports
- on board revisions 32+ also various peripheral resets and another voltage regulator enable pin

OK all pretty straight-forward devices built on top of GPIO in something like device tree or ACPI etc.

Quoted 8 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +5 -0
@@ -1,3 +1,19 @@
+What:          /sys/bus/i2c/devices/<mcu_device>/front_button_mode
+Date:          August 2023
+KernelVersion: 6.6
+Contact:       Marek Behún <[email protected]>
+Description:   (RW) The front button on the Turris Omnia router can be

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu line 15

Quoted 10 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +10 -0
+               configured either to change the intensity of all the LEDs on the
+               front panel, or to send the press event to the CPU as an
+               interrupt.
+
+               This file switches between these two modes:
+               - "mcu" makes the button press event be handled by the MCU to
+                 change the LEDs panel intensity.
+               - "cpu" makes the button press event be handled by the CPU.
+
+               Format: %s.

I understand what this does, but why does this have to be configured at runtime from userspace sysfs? Why can't this just be a property in device tree or ACPI (etc)? I don't imagine a user is going to change this back and forth are they? They likely want one or another.

drivers/platform/cznic/turris-omnia-mcu-base.c static int omnia_mcu_probe(struct i2c_client *client) · line 227

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +3 -0
@@ -222,9 +222,20 @@ static int omnia_mcu_probe(struct i2c_client *client)
                return -ENODATA;
        }
 
+       ret = omnia_mcu_register_gpiochip(mcu);
+       if (ret)
+               return ret;

I guess it's OK to have a GPIOchip with the rest of the stuff, there are a few precedents, such as drivers/bcma.

drivers/platform/cznic/turris-omnia-mcu-base.c static int omnia_mcu_probe(struct i2c_client *client) · line 228

Quoted 1 line
drivers/platform/cznic/turris-omnia-mcu-base.c +1 -0
+static const char * const omnia_mcu_gpio_names[64] = {

I would name these _templates since it is not the final names that will be used. Very nice with all debug names though!

Quoted 23 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +23 -0
+#define _DEF_GPIO(_cmd, _ctl_cmd, _bit, _ctl_bit, _int_bit, _feat, _feat_mask) \
+       {                                                               \
+               .cmd = _cmd,                                            \
+               .ctl_cmd = _ctl_cmd,                                    \
+               .bit = _bit,                                            \
+               .ctl_bit = _ctl_bit,                                    \
+               .int_bit = _int_bit,                                    \
+               .feat = _feat,                                          \
+               .feat_mask = _feat_mask,                                \
+       }
+#define _DEF_GPIO_STS(_name) \
+       _DEF_GPIO(CMD_GET_STATUS_WORD, 0, STS_ ## _name, 0, INT_ ## _name, 0, 0)
+#define _DEF_GPIO_CTL(_name) \
+       _DEF_GPIO(CMD_GET_STATUS_WORD, CMD_GENERAL_CONTROL, STS_ ## _name, \
+                 CTL_ ## _name, 0, 0, 0)
+#define _DEF_GPIO_EXT_STS(_name, _feat) \
+       _DEF_GPIO(CMD_GET_EXT_STATUS_DWORD, 0, EXT_STS_ ## _name, 0, \
+                 INT_ ## _name, FEAT_ ## _feat | FEAT_EXT_CMDS, \
+                 FEAT_ ## _feat | FEAT_EXT_CMDS)
+#define _DEF_GPIO_EXT_STS_LED(_name, _ledext) \
+       _DEF_GPIO(CMD_GET_EXT_STATUS_DWORD, 0, EXT_STS_ ## _name, 0, \
+                 INT_ ## _name, FEAT_LED_STATE_ ## _ledext, \
+                 FEAT_LED_STATE_EXT_MASK)

drivers/platform/cznic/turris-omnia-mcu-base.c static int omnia_mcu_probe(struct i2c_client *client)

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +10 -0
+#define _DEF_GPIO_EXT_STS_LEDALL(_name) \
+       _DEF_GPIO(CMD_GET_EXT_STATUS_DWORD, 0, EXT_STS_ ## _name, 0, \
+                 INT_ ## _name, FEAT_LED_STATE_EXT_MASK, 0)
+#define _DEF_GPIO_EXT_CTL(_name, _feat) \
+       _DEF_GPIO(CMD_GET_EXT_CONTROL_STATUS, CMD_EXT_CONTROL, \
+                 EXT_CTL_ ## _name, EXT_CTL_ ## _name, 0, \
+                 FEAT_ ## _feat | FEAT_EXT_CMDS, \
+                 FEAT_ ## _feat | FEAT_EXT_CMDS)
+#define _DEF_INT(_name) \
+       _DEF_GPIO(0, 0, 0, 0, INT_ ## _name, 0, 0)

Ugh that's really hard to read and understand, but I can't think of anything better
so I guess we live with it.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+static const struct omnia_gpio {
+       u8 cmd, ctl_cmd;
+       u32 bit, ctl_bit;
+       u32 int_bit;

If they are really just ONE bit I would actually use an int, encode the bit number rather than the mask, and then use BIT( ..->int_bit) from <linux/bits.h> everywhere I need a mask. No strong preference though.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +5 -0
+/* mapping from interrupts to indexes of GPIOs in the omnia_gpios array */
+static const unsigned int omnia_int_to_gpio_idx[32] = {
+       [ilog2(INT_CARD_DET)]           = 4,
+       [ilog2(INT_MSATA_IND)]          = 5,
+       [ilog2(INT_USB30_OVC)]          = 6,

ilog2 is a bit unituitive for a programmer, are you a schooled mathematician Marek? ;)

It is also a consequence of using a bitmask rather than a bit number in the struct, all ilog2 does is fls-1. So what about just putting the bit number in the struct and then all of these assignments will look natural as well.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+/* index of PHY_SFP GPIO in the omnia_gpios array */
+enum {
+       OMNIA_GPIO_PHY_SFP_OFFSET = 54,
+};

I would just use a define for this, the enum isn't very useful for singular values.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +6 -0
+static int omnia_ctl_cmd(struct omnia_mcu *mcu, u8 cmd, u16 val, u16 mask)
+{
+       int err;
+
+       mutex_lock(&mcu->lock);
+       err = omnia_ctl_cmd_unlocked(mcu, cmd, val, mask);

We just discussed this on an unrelated topic: *_unlocked means it should be called without need for the appropriate mutex to be locked, since it clearly requires &mcu->lock to be taken, this should be omnia_ctl_cmd_locked() as in "call this with the lock held".

I did a quick grep _locked to convince myself about this. Obvious exampled include wait_event_interruptible_locked_irq() or wake_up_locked() in the core kernel infrastructure that clearly states (include/linux/wait.h for wait_event_interruptible_locked():
"It must be called with wq.lock being held."

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +2 -0
+static int omnia_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{

(...)

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +3 -0
+       if (gpio->cmd == CMD_GET_STATUS_WORD &&
+           !(mcu->features & FEAT_NEW_INT_API))
+               return !!(mcu->last_status & gpio->bit);

So I expect something like return !!(mcu->last_status & BIT(gpio->bit));

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+static int omnia_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask,
+                                  unsigned long *bits)
+{
+       mutex_lock(&mcu->lock);

(...)

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +2 -0
+               if (err)
+                       goto err_unlock;

(...)

Quoted 2 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +2 -0
+       if (err)
+               goto err_unlock;

(...)

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+       if (err)
+               goto err_unlock;
+
+       mutex_unlock(&mcu->lock);

This function is kind of a good example of where using scoped guards from <linux/cleanup.h> can simplify the code. After the initial lock you can just return on error and let the cleanup handler unlock the mutex, just guard(mutex)(&mcu->lock);

In the beginning and then it's done, the lock will be held until the function is exited.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 3 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +3 -0
+static void omnia_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+                                   unsigned long *bits)
+{

(...)

same here.

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +6 -0
+static int omnia_gpio_init_valid_mask(struct gpio_chip *gc,
+                                     unsigned long *valid_mask,
+                                     unsigned int ngpios)
+{
+       struct omnia_mcu *mcu = gpiochip_get_data(gc);
+

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +10 -0
+       bitmap_zero(valid_mask, ngpios);
+
+       for (int i = 0; i < ngpios; ++i) {
+               const struct omnia_gpio *gpio = &omnia_gpios[i];
+
+               if (!gpio->cmd && !gpio->int_bit)
+                       continue;
+
+               if (omnia_gpio_available(mcu, gpio))
+                       set_bit(i, valid_mask);

Speaking of locks, isn't this where you should use __set_bit() rather than set_bit()?

__set_bit() is incomprehensible shorthand for "set_bit_nonatomic".

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +9 -0
+static void omnia_irq_shutdown(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct omnia_mcu *mcu = gpiochip_get_data(gc);
+       irq_hw_number_t hwirq = irqd_to_hwirq(d);
+       u32 bit = omnia_gpios[hwirq].int_bit;
+
+       mcu->rising &= ~bit;
+       mcu->falling &= ~bit;

Yeah so here it would be BIT(bit) etc.

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
+       if (type & IRQ_TYPE_EDGE_RISING)
+               mcu->rising |= bit;
+       else
+               mcu->rising &= ~bit;

And with bits in place of bitmasks these would be

if ()
__set_bit(bit, mcu->rising);
else
__clear_bit(bit, mcu->rising);

etc

Quoted 6 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +6 -0
+static void omnia_irq_init_valid_mask(struct gpio_chip *gc,
+                                     unsigned long *valid_mask,
+                                     unsigned int ngpios)
+{
+       struct omnia_mcu *mcu = gpiochip_get_data(gc);
+

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +10 -0
+       bitmap_zero(valid_mask, ngpios);
+
+       for (int i = 0; i < ngpios; ++i) {
+               const struct omnia_gpio *gpio = &omnia_gpios[i];
+
+               if (!gpio->int_bit)
+                       continue;
+
+               if (omnia_gpio_available(mcu, gpio))
+                       set_bit(i, valid_mask);

__set_bit()

Quoted 5 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +5 -0
+static bool omnia_irq_read_pending_new(struct omnia_mcu *mcu,
+                                      unsigned long *pending)
+{
+       u32 rising, falling;
+       u8 reply[8] = {};

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +10 -0
+       size_t len;
+       int err;
+
+       /*
+        * Determine how many bytes we need to read from the reply to the
+        * CMD_GET_INT_AND_CLEAR command in order to retrieve all unmasked
+        * interrupts.
+        */
+       len = max(((ilog2(mcu->rising & mcu->mask) >> 3) << 1) + 1,
+                 ((ilog2(mcu->falling & mcu->mask) >> 3) << 1) + 2);

Really hard to read, and again easier if we store the bit number rather than the bitmask.

Quoted 1 line

+ mutex_lock(&mcu->lock);

Scoped guard?

Quoted 9 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +9 -0
+static void button_release_emul_fn(struct work_struct *work)
+{
+       struct omnia_mcu *mcu = container_of(to_delayed_work(work),
+                                            struct omnia_mcu,
+                                            button_release_emul_work);
+
+       mcu->button_pressed_emul = false;
+       generic_handle_irq_safe(mcu->client->irq);
+}

(...)

Quoted 20 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +20 -0
+       /*
+        * The old firmware triggers an interrupt whenever status word changes,
+        * but does not inform about which bits rose or fell. We need to compute
+        * this here by comparing with the last status word value.
+        *
+        * The STS_BUTTON_PRESSED bit needs special handling, because the old
+        * firmware clears the STS_BUTTON_PRESSED bit on successful completion
+        * of the CMD_GET_STATUS_WORD command, resulting in another interrupt:
+        * - first we get an interrupt, we read the status word where
+        *   STS_BUTTON_PRESSED is present,
+        * - MCU clears the STS_BUTTON_PRESSED bit because we read the status
+        *   word,
+        * - we get another interrupt because the status word changed again
+        *   (STS_BUTTON_PRESSED was cleared).
+        *
+        * The gpiolib-cdev, gpiolib-sysfs and gpio-keys input driver all call
+        * the gpiochip's .get() method after an edge event on a requested GPIO
+        * occurs.
+        *
+        * We ensure that the .get() method reads 1 for the button GPIO for some

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-base.c +10 -0
+        * time.
+        */
+
+       if (status & STS_BUTTON_PRESSED) {
+               mcu->button_pressed_emul = true;
+               mod_delayed_work(system_wq, &mcu->button_release_emul_work,
+                                msecs_to_jiffies(50));
+       } else if (mcu->button_pressed_emul) {
+               status |= STS_BUTTON_PRESSED;
+       }

I feel a bit icky about this, would be best if Dmitry Torokhov review such input subsystem-related things.

It looks like a reimplementation of drivers/input/keyboard/gpio_keys.c so why not just connect a gpio-keys device using either device tree or a software node? (Dmitry knows all about how to create proper software nodes for stuff like this, DT is self-evident.)

Quoted 1 line

+ mcu->gc.names = omnia_mcu_gpio_names;

Do we really support format strings in these names? I don't think so, or I'm wrong about my own code... (Which wouldn't be the first time.)

drivers/platform/cznic/turris-omnia-mcu-base.c

Quoted 4 lines

#include <asm/byteorder.h>

drivers/platform/cznic/turris-omnia-mcu-base.c +2 -0
+#include <linux/gpio/driver.h>
 #include <linux/i2c.h>
+#include <linux/log2.h>

I think we want to get rid of log2.h from this driver.

Yours,
Linus Walleij

#20 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 1 line · Linus Walleij

On Tue, Sep 19, 2023 at 12:38 PM Marek Behún <[email protected]> wrote:

...

Quoted 11 lines

drivers/platform/cznic/turris-omnia-mcu-gpio.c line 532

Quoted 4 lines
drivers/platform/cznic/turris-omnia-mcu-gpio.c new file +4 -0
+       if (type & IRQ_TYPE_EDGE_RISING)
+               mcu->rising |= bit;
+       else
+               mcu->rising &= ~bit;

And with bits in place of bitmasks these would be

if ()
__set_bit(bit, mcu->rising);
else
__clear_bit(bit, mcu->rising);

More precisely __assign_bit() in this case.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#21 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

In reply to Linus Walleij

Hello Linus,

I am sending some comments/questions to your review. To those of your comments that I do not reply, I will incorporate your suggestion.

Quoted 15 lines · On Wed, 20 Sep 2023 13:58:37 +0200 Linus Walleij <[email protected]>…
Quoted 15 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +12 -0
@@ -1,3 +1,19 @@
+What:          /sys/bus/i2c/devices/<mcu_device>/front_button_mode
+Date:          August 2023
+KernelVersion: 6.6
+Contact:       Marek Behún <[email protected]>
+Description:   (RW) The front button on the Turris Omnia router can be
+               configured either to change the intensity of all the LEDs on the
+               front panel, or to send the press event to the CPU as an
+               interrupt.
+
+               This file switches between these two modes:
+               - "mcu" makes the button press event be handled by the MCU to
+                 change the LEDs panel intensity.
Quoted 8 lines

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu line 15

Quoted 3 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +3 -0
+               - "cpu" makes the button press event be handled by the CPU.
+
+               Format: %s.  

I understand what this does, but why does this have to be configured at runtime from userspace sysfs? Why can't this just be a property in device tree or ACPI (etc)? I don't imagine a user is going to change this back and forth are they? They likely want one or another.

Yes, we really do expect this, we even want to have some applications on the router to do this.

Quoted 9 lines

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu line 19

Quoted 4 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +4 -0
+static const struct omnia_gpio {
+       u8 cmd, ctl_cmd;
+       u32 bit, ctl_bit;
+       u32 int_bit;  

If they are really just ONE bit I would actually use an int, encode the bit number rather than the mask, and then use BIT( ..->int_bit) from <linux/bits.h> everywhere I need a mask. No strong preference though.

See below.

Quoted 9 lines

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu

Quoted 5 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +5 -0
+/* mapping from interrupts to indexes of GPIOs in the omnia_gpios array */
+static const unsigned int omnia_int_to_gpio_idx[32] = {
+       [ilog2(INT_CARD_DET)]           = 4,
+       [ilog2(INT_MSATA_IND)]          = 5,
+       [ilog2(INT_USB30_OVC)]          = 6,  

ilog2 is a bit unituitive for a programmer, are you a schooled mathematician Marek? ;)

Quoted 3 lines

It is also a consequence of using a bitmask rather than a bit number in the struct, all ilog2 does is fls-1. So what about just putting the bit number in the struct and then all of these assignments will look natural as well.

Not really, altough I studied advanced analysis :) I guess ilog2 might confuse some people. Would bf_shf() (as in bitfield shift) from linux/bitfield.h be better?

So, the reasoning behind why the bits are defined as such in linux/turris-omnia-mcu-interface.h (from patch 2/7):

- if you look for example at enum sts_word_e, the entries correspond to fields withing the word. And not all fields are 1-bit wide. For example the STS_MCU_TYPE field is 2 bits wide. Similarly the FEAT_LED_STATE_EXT_MASK field in enum features_e

- I have seen similar code within many parts of the kernel and have adopted it. I think many developers are accustomed to it. Look for example into how register fields are defined in the mv88e6xxx driver header files

In order to make maintaining smoother, I would like to have the turris-omnia-mcu-interface.h file be one-to-one copy of the corresponding file within the MCU firmware repository
(https://gitlab.nic.cz/turris/hw/omnia_hw_ctrl/-/blob/master/src/drivers/i2c_iface.h)
wherein the constants are defined this way and used throughout the codebase.
Since I am the author and maintainer of the (current versions of) MCU firmware, I could take your suggestion and change the constants, and rewrite the firmware.
But taking into consideration the above two points, I am reluctant to do so.

So... do you insist on this change? I would rather keep it as it is, but I can change the ilog2() to bf_shf() to make it easier for non-mathematicians.

Quoted 10 lines
Quoted 10 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +10 -0
+static bool omnia_irq_read_pending_new(struct omnia_mcu *mcu,
+                                      unsigned long *pending)
+{
+       u32 rising, falling;
+       u8 reply[8] = {};
+       size_t len;
+       int err;
+
+       /*
+        * Determine how many bytes we need to read from the reply to the
Quoted 8 lines

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu

Quoted 5 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +5 -0
+        * CMD_GET_INT_AND_CLEAR command in order to retrieve all unmasked
+        * interrupts.
+        */
+       len = max(((ilog2(mcu->rising & mcu->mask) >> 3) << 1) + 1,
+                 ((ilog2(mcu->falling & mcu->mask) >> 3) << 1) + 2);  

Really hard to read, and again easier if we store the bit number rather than the bitmask.

No. Even if I take your suggestion and rewrite this to store bit offsets instead of bit masks, here I really need to compute this, since there might be multiple bits set. For example there might be bits 4, 5, and 14 set in mcu->rising (mcu->rising = 0x4030).
I need to determine how many bytes of the I2C reply I will need, and for that I need to know the position of the first set bit. (I could keep a track of the maximum set bit, but honestly this seems to me much easier).

I could use ffs(x) instead of ilog2(x) + 1.

Quoted 41 lines
Quoted 9 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +9 -0
+static void button_release_emul_fn(struct work_struct *work)
+{
+       struct omnia_mcu *mcu = container_of(to_delayed_work(work),
+                                            struct omnia_mcu,
+                                            button_release_emul_work);
+
+       mcu->button_pressed_emul = false;
+       generic_handle_irq_safe(mcu->client->irq);
+}  

(...)

Quoted 20 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +20 -0
+       /*
+        * The old firmware triggers an interrupt whenever status word changes,
+        * but does not inform about which bits rose or fell. We need to compute
+        * this here by comparing with the last status word value.
+        *
+        * The STS_BUTTON_PRESSED bit needs special handling, because the old
+        * firmware clears the STS_BUTTON_PRESSED bit on successful completion
+        * of the CMD_GET_STATUS_WORD command, resulting in another interrupt:
+        * - first we get an interrupt, we read the status word where
+        *   STS_BUTTON_PRESSED is present,
+        * - MCU clears the STS_BUTTON_PRESSED bit because we read the status
+        *   word,
+        * - we get another interrupt because the status word changed again
+        *   (STS_BUTTON_PRESSED was cleared).
+        *
+        * The gpiolib-cdev, gpiolib-sysfs and gpio-keys input driver all call
+        * the gpiochip's .get() method after an edge event on a requested GPIO
+        * occurs.
+        *
+        * We ensure that the .get() method reads 1 for the button GPIO for some

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu

Quoted 10 lines
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +10 -0
+        * time.
+        */
+
+       if (status & STS_BUTTON_PRESSED) {
+               mcu->button_pressed_emul = true;
+               mod_delayed_work(system_wq, &mcu->button_release_emul_work,
+                                msecs_to_jiffies(50));
+       } else if (mcu->button_pressed_emul) {
+               status |= STS_BUTTON_PRESSED;
+       }  
Quoted 7 lines

I feel a bit icky about this, would be best if Dmitry Torokhov review such input subsystem-related things.

It looks like a reimplementation of drivers/input/keyboard/gpio_keys.c so why not just connect a gpio-keys device using either device tree or a software node? (Dmitry knows all about how to create proper software nodes for stuff like this, DT is self-evident.)

You have it the other way around :-) This is done in order so that the gpio-keys driver can use it. Let me explain:

The new versions of the MCU firmware support rising and falling edges for GPIO events. So when button is pressed, we get an event, and when it is released, we get an event. gpio-keys works perfectly.

The problem with old MCU firmware is that the interrupt API was tragically designed, which resulted in that on button press event, we get an interrupt, and when we read the status word via I2C, the bit gets cleared immediately (and we get another interrupt because the bit is cleared). There is no interrupt when the button is truly released. This fake "button release interrupt" comes so fast that the gpio_keys driver ignores it even if we set debounce_interval to smallest possible value of 1ms (if I understand the gpio_keys driver correctly).

So this workaround is there so that we generate a GPIO release event for the button only after 50ms, if the MCU is running old firmware.

A similar thing is implemented in the gpio-keys driver when there is no release event: if the event source is an interrupt descriptor instead of a gpio descriptor, the gpio-keys will emulate button release. I could use this, but then we would need to change the device-tree based on whether the MCU is running old firmware vs new, which I want to avoid. I also want to avoid software nodes, since I want the button to be properly described in device-tree.

Honestly, because of this whole debacle I was also considering writing my own small input driver within this turris-omnia-mcu driver...

Quoted 5 lines
Quoted 1 line

+ mcu->gc.names = omnia_mcu_gpio_names;

Do we really support format strings in these names? I don't think so, or I'm wrong about my own code... (Which wouldn't be the first time.)

Yes we do, but only one :) The number of the GPIO.

Quoted 6 lines

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu

Quoted 4 lines

#include <asm/byteorder.h>

Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +2 -0
+#include <linux/gpio/driver.h>
 #include <linux/i2c.h>
+#include <linux/log2.h>  

I think we want to get rid of log2.h from this driver.

See above, even if I take your suggestion, I will still need the position of the first set bit within word in omnia_irq_read_pending_new().

Marek

author maintainer
#22 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 1 line · On Thu, 21 Sep 2023 21:45:57 +0200 Marek Behún <[email protected]> wrote:

I could use ffs(x) instead of ilog2(x) + 1.

Pardon me, I meant fls(). Or maybe get_bitmask_order() from linux/bitops.h.

Marek

#23 lore raw

Re: [PATCH v2 3/7] platform: cznic: turris-omnia-mcu: Add support for MCU connected GPIOs

Quoted 7 lines · Marek Behún
Quoted 1 line · On Thu, 21 Sep 2023 21:45:57 +0200 Marek Behún <[email protected]> wrote:

I could use ffs(x) instead of ilog2(x) + 1.

Pardon me, I meant fls(). Or maybe get_bitmask_order() from linux/bitops.h.

In any case it's bitops.h APIs that you will need and I think it's fine and Linus will approve that.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
author maintainer
#24 lore raw

PATCH v2 4/7 platform: cznic: turris-omnia-mcu: Add support for poweroff and wakeup

In reply to Marek Behún

Add support for true board poweroff (MCU can disable all unnecessary voltage regulators) and wakeup at a specified time, implemented via a RTC driver so that the rtcwake utility can be used to configure it.

---
 .../sysfs-bus-i2c-devices-turris-omnia-mcu    |  16 ++
 drivers/platform/cznic/Kconfig                |   4 +
 drivers/platform/cznic/Makefile               |   1 +
 .../platform/cznic/turris-omnia-mcu-base.c    |   4 +
 .../platform/cznic/turris-omnia-mcu-gpio.c    |   2 -
 .../cznic/turris-omnia-mcu-sys-off-wakeup.c   | 245 ++++++++++++++++++
 drivers/platform/cznic/turris-omnia-mcu.h     |  19 ++
 7 files changed, 289 insertions(+), 2 deletions(-)
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +16 -0
index c0efdf1b3803..d668b8e999e6 100644
@@ -14,6 +14,22 @@ Description:	(RW) The front button on the Turris Omnia router can be
 
 		Format: %s.
 
+What:		/sys/bus/i2c/devices/<mcu_device>/front_button_poweron
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RW) Newer versions of the microcontroller firmware of the
+		Turris Omnia router support powering off the router into true
+		low power mode. The router can be powered on by pressing the
+		front button.
+
+		This file configures whether front button power on is enabled.
+
+		This file is present only if the power off feature is supported
+		by the firmware.
+
+		Format: %i.
+
 What:		/sys/bus/i2c/devices/<mcu_device>/fw_features
 Date:		August 2023
 KernelVersion:	6.6
drivers/platform/cznic/Kconfig +4 -0
index 3a8c3edcd7e6..0a752aa654fa 100644
@@ -19,10 +19,14 @@ config TURRIS_OMNIA_MCU
 	depends on I2C
 	select GPIOLIB
 	select GPIOLIB_IRQCHIP
+	select RTC_CLASS
 	help
 	  Say Y here to add support for the features implemented by the
 	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
 	  The features include:
+	  - board poweroff into true low power mode (with voltage regulators
+	    disabled) and the ability to configure wake up from this mode (via
+	    rtcwake)
 	  - GPIO pins
 	    - to get front button press events (the front button can be
 	      configured either to generate press events to the CPU or to change
drivers/platform/cznic/Makefile +1 -0
index a6177f5b4fff..6f1470d1f673 100644
@@ -3,3 +3,4 @@
 obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
 turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
 turris-omnia-mcu-objs		+= turris-omnia-mcu-gpio.o
+turris-omnia-mcu-objs		+= turris-omnia-mcu-sys-off-wakeup.o
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
index 2954151468e5..b507f81ebe0a 100644
@@ -226,6 +226,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
 	if (ret)
 		return ret;
 
+	ret = omnia_mcu_register_sys_off_and_wakeup(mcu);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
drivers/platform/cznic/turris-omnia-mcu-gpio.c +0 -2
index e8d5eeb4eb31..3d34a32bc7b0 100644
@@ -5,8 +5,6 @@
  * 2023 by Marek Behún <[email protected]>
  */
 
-#include <asm/unaligned.h>
-
 #include "turris-omnia-mcu.h"
 
 static const char * const omnia_mcu_gpio_names[64] = {
drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c new file +245 -0

245 lines not on this page · raw message

drivers/platform/cznic/turris-omnia-mcu.h +19 -0
index b5802240aa8d..c40ac07be5f5 100644
@@ -9,10 +9,12 @@
 #define __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H
 
 #include <asm/byteorder.h>
+#include <asm/unaligned.h>
 #include <linux/gpio/driver.h>
 #include <linux/i2c.h>
 #include <linux/log2.h>
 #include <linux/mutex.h>
+#include <linux/rtc.h>
 #include <linux/turris-omnia-mcu-interface.h>
 #include <linux/workqueue.h>
 
@@ -29,6 +31,11 @@ struct omnia_mcu {
 	u16 last_status;
 	struct delayed_work button_release_emul_work;
 	bool button_pressed_emul;
+
+	/* RTC device for configuring wake-up */
+	struct rtc_device *rtcdev;
+	u32 rtc_alarm;
+	bool front_button_poweron;
 };
 
 static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
@@ -39,6 +46,17 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
 	return ret < 0 ? ret : 0;
 }
 
+static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
+				      u32 val)
+{
+	u8 buf[5];
+
+	buf[0] = cmd;
+	put_unaligned_le32(val, &buf[1]);
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
 static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, void *reply,
 				 unsigned int len)
 {
@@ -117,5 +135,6 @@ static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
 
 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
 void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu);
+int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
 
 #endif /* __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H */
Signature · 1 line
2.41.0
author maintainer
#25 lore raw

Re: [PATCH v2 4/7] platform: cznic: turris-omnia-mcu: Add support for poweroff and wakeup

Allesandro, Alexandre, would you be able to find some time to review this patch?

Marek

Quoted 404 lines · On Tue, 19 Sep 2023 12:38:12 +0200 Marek Behún <[email protected]> wrote:

Add support for true board poweroff (MCU can disable all unnecessary voltage regulators) and wakeup at a specified time, implemented via a RTC driver so that the rtcwake utility can be used to configure it.

---
 .../sysfs-bus-i2c-devices-turris-omnia-mcu    |  16 ++
 drivers/platform/cznic/Kconfig                |   4 +
 drivers/platform/cznic/Makefile               |   1 +
 .../platform/cznic/turris-omnia-mcu-base.c    |   4 +
 .../platform/cznic/turris-omnia-mcu-gpio.c    |   2 -
 .../cznic/turris-omnia-mcu-sys-off-wakeup.c   | 245 ++++++++++++++++++
 drivers/platform/cznic/turris-omnia-mcu.h     |  19 ++
 7 files changed, 289 insertions(+), 2 deletions(-)
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu +16 -0
index c0efdf1b3803..d668b8e999e6 100644
@@ -14,6 +14,22 @@ Description:	(RW) The front button on the Turris Omnia router can be
 
 		Format: %s.
 
+What:		/sys/bus/i2c/devices/<mcu_device>/front_button_poweron
+Date:		August 2023
+KernelVersion:	6.6
+Contact:	Marek Behún <[email protected]>
+Description:	(RW) Newer versions of the microcontroller firmware of the
+		Turris Omnia router support powering off the router into true
+		low power mode. The router can be powered on by pressing the
+		front button.
+
+		This file configures whether front button power on is enabled.
+
+		This file is present only if the power off feature is supported
+		by the firmware.
+
+		Format: %i.
+
 What:		/sys/bus/i2c/devices/<mcu_device>/fw_features
 Date:		August 2023
 KernelVersion:	6.6
drivers/platform/cznic/Kconfig +4 -0
index 3a8c3edcd7e6..0a752aa654fa 100644
@@ -19,10 +19,14 @@ config TURRIS_OMNIA_MCU
 	depends on I2C
 	select GPIOLIB
 	select GPIOLIB_IRQCHIP
+	select RTC_CLASS
 	help
 	  Say Y here to add support for the features implemented by the
 	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
 	  The features include:
+	  - board poweroff into true low power mode (with voltage regulators
+	    disabled) and the ability to configure wake up from this mode (via
+	    rtcwake)
 	  - GPIO pins
 	    - to get front button press events (the front button can be
 	      configured either to generate press events to the CPU or to change
drivers/platform/cznic/Makefile +1 -0
index a6177f5b4fff..6f1470d1f673 100644
@@ -3,3 +3,4 @@
 obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
 turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
 turris-omnia-mcu-objs		+= turris-omnia-mcu-gpio.o
+turris-omnia-mcu-objs		+= turris-omnia-mcu-sys-off-wakeup.o
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
index 2954151468e5..b507f81ebe0a 100644
@@ -226,6 +226,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
 	if (ret)
 		return ret;
 
+	ret = omnia_mcu_register_sys_off_and_wakeup(mcu);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
drivers/platform/cznic/turris-omnia-mcu-gpio.c +0 -2
index e8d5eeb4eb31..3d34a32bc7b0 100644
@@ -5,8 +5,6 @@
  * 2023 by Marek Behún <[email protected]>
  */
 
-#include <asm/unaligned.h>
-
 #include "turris-omnia-mcu.h"
 
 static const char * const omnia_mcu_gpio_names[64] = {
drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c new file +245 -0
new file mode 100644
index 000000000000..07ae92d819e3
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CZ.NIC's Turris Omnia MCU system off and RTC wakeup driver
+ *
+ * This is not a true RTC driver (in the sense that it does not provide a
+ * real-time clock), rather the MCU implements a wakeup from powered off state
+ * at a specified time relative to MCU boot, and we expose this feature via RTC
+ * alarm, so that it can be used via the rtcwake command, which is the standard
+ * Linux command for this.
+ *
+ * 2023 by Marek Behún <[email protected]>
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+
+#include "turris-omnia-mcu.h"
+
+static int omnia_get_uptime_wakeup(const struct i2c_client *client, u32 *uptime,
+				   u32 *wakeup)
+{
+	u32 reply[2];
+	int err;
+
+	err = omnia_cmd_read(client, CMD_GET_UPTIME_AND_WAKEUP, reply,
+			     sizeof(reply));
+	if (err)
+		return err;
+
+	if (uptime)
+		*uptime = le32_to_cpu(reply[0]);
+
+	if (wakeup)
+		*wakeup = le32_to_cpu(reply[1]);
+
+	return 0;
+}
+
+static int omnia_read_time(struct device *dev, struct rtc_time *tm)
+{
+	u32 uptime;
+	int err;
+
+	err = omnia_get_uptime_wakeup(to_i2c_client(dev), &uptime, NULL);
+	if (err)
+		return err;
+
+	rtc_time64_to_tm(uptime, tm);
+
+	return 0;
+}
+
+static int omnia_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct omnia_mcu *mcu = i2c_get_clientdata(client);
+	u32 wakeup;
+	int err;
+
+	err = omnia_get_uptime_wakeup(client, NULL, &wakeup);
+	if (err)
+		return err;
+
+	alrm->enabled = !!wakeup;
+	rtc_time64_to_tm(wakeup ?: mcu->rtc_alarm, &alrm->time);
+
+	return 0;
+}
+
+static int omnia_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct omnia_mcu *mcu = i2c_get_clientdata(client);
+
+	mcu->rtc_alarm = rtc_tm_to_time64(&alrm->time);
+
+	if (alrm->enabled)
+		return omnia_cmd_write_u32(client, CMD_SET_WAKEUP,
+					   mcu->rtc_alarm);
+	else
+		return 0;
+}
+
+static int omnia_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct omnia_mcu *mcu = i2c_get_clientdata(client);
+
+	return omnia_cmd_write_u32(client, CMD_SET_WAKEUP,
+				   enabled ? mcu->rtc_alarm : 0);
+}
+
+static const struct rtc_class_ops omnia_rtc_ops = {
+	.read_time		= omnia_read_time,
+	.read_alarm		= omnia_read_alarm,
+	.set_alarm		= omnia_set_alarm,
+	.alarm_irq_enable	= omnia_alarm_irq_enable,
+};
+
+static int omnia_power_off(struct sys_off_data *data)
+{
+	struct omnia_mcu *mcu = data->cb_data;
+	u8 cmd[9];
+	u32 tmp;
+	u16 arg;
+	int err;
+
+	if (mcu->front_button_poweron)
+		arg = CMD_POWER_OFF_POWERON_BUTTON;
+	else
+		arg = 0;
+
+	cmd[0] = CMD_POWER_OFF;
+	put_unaligned_le16(CMD_POWER_OFF_MAGIC, &cmd[1]);
+	put_unaligned_le16(arg, &cmd[3]);
+
+	tmp = cpu_to_be32(get_unaligned_le32(&cmd[1]));
+	put_unaligned_le32(crc32_be(0xffffffff, (void *)&tmp, sizeof(tmp)),
+			   &cmd[5]);
+
+	err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
+	if (err)
+		dev_err(&mcu->client->dev,
+			"Unable to send the poweroff command: %d\n", err);
+
+	return NOTIFY_DONE;
+}
+
+static int omnia_restart(struct sys_off_data *data)
+{
+	struct omnia_mcu *mcu = data->cb_data;
+	u8 cmd[3];
+	int err;
+
+	cmd[0] = CMD_GENERAL_CONTROL;
+
+	if (reboot_mode == REBOOT_HARD)
+		cmd[1] = cmd[2] = CTL_HARD_RST;
+	else
+		cmd[1] = cmd[2] = CTL_LIGHT_RST;
+
+	err = omnia_cmd_write(mcu->client, cmd, sizeof(cmd));
+	if (err)
+		dev_err(&mcu->client->dev,
+			"Unable to send the restart command: %d\n", err);
+
+	/*
+	 * MCU needs a little bit to process the I2C command, otherwise it will
+	 * do a light reset based on SOC SYSRES_OUT pin.
+	 */
+	mdelay(1);
+
+	return NOTIFY_DONE;
+}
+
+static ssize_t front_button_poweron_show(struct device *dev,
+					 struct device_attribute *a, char *buf)
+{
+	struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev));
+
+	return sysfs_emit(buf, "%d\n", mcu->front_button_poweron);
+}
+
+static ssize_t front_button_poweron_store(struct device *dev,
+					  struct device_attribute *a,
+					  const char *buf, size_t count)
+{
+	struct omnia_mcu *mcu = i2c_get_clientdata(to_i2c_client(dev));
+	bool val;
+
+	if (kstrtobool(buf, &val) < 0)
+		return -EINVAL;
+
+	mcu->front_button_poweron = val;
+
+	return count;
+}
+static DEVICE_ATTR_RW(front_button_poweron);
+
+static struct attribute *omnia_mcu_poweroff_attrs[] = {
+	&dev_attr_front_button_poweron.attr,
+	NULL,
+};
+
+static const struct attribute_group omnia_mcu_poweroff_group = {
+	.attrs = omnia_mcu_poweroff_attrs,
+};
+
+int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu)
+{
+	struct device *dev = &mcu->client->dev;
+	int err;
+
+	/* MCU restart is always available */
+	err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
+					    SYS_OFF_PRIO_FIRMWARE,
+					    omnia_restart, mcu);
+	if (err) {
+		dev_err(dev, "Cannot register system restart handler: %d\n",
+			err);
+		return err;
+	}
+
+	/*
+	 * poweroff and wakeup are available only if POWEROFF_WAKEUP feature is
+	 * present
+	 */
+	if (!(mcu->features & FEAT_POWEROFF_WAKEUP))
+		return 0;
+
+	err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
+					    SYS_OFF_PRIO_FIRMWARE,
+					    omnia_power_off, mcu);
+	if (err) {
+		dev_err(dev, "Cannot register system power off handler: %d\n",
+			err);
+		return err;
+	}
+
+	mcu->rtcdev = devm_rtc_allocate_device(dev);
+	if (IS_ERR(mcu->rtcdev)) {
+		err = PTR_ERR(mcu->rtcdev);
+		dev_err(dev, "Cannot allocate RTC device: %d\n", err);
+		return err;
+	}
+
+	mcu->rtcdev->ops = &omnia_rtc_ops;
+	mcu->rtcdev->range_max = U32_MAX;
+
+	err = devm_rtc_register_device(mcu->rtcdev);
+	if (err) {
+		dev_err(dev, "Cannot register RTC device: %d\n", err);
+		return err;
+	}
+
+	mcu->front_button_poweron = true;
+
+	err = devm_device_add_group(dev, &omnia_mcu_poweroff_group);
+	if (err)
+		dev_err(dev, "Cannot add poweroff sysfs attribute group: %d\n",
+			err);
+
+	return err;
+}
drivers/platform/cznic/turris-omnia-mcu.h +19 -0
index b5802240aa8d..c40ac07be5f5 100644
@@ -9,10 +9,12 @@
 #define __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H
 
 #include <asm/byteorder.h>
+#include <asm/unaligned.h>
 #include <linux/gpio/driver.h>
 #include <linux/i2c.h>
 #include <linux/log2.h>
 #include <linux/mutex.h>
+#include <linux/rtc.h>
 #include <linux/turris-omnia-mcu-interface.h>
 #include <linux/workqueue.h>
 
@@ -29,6 +31,11 @@ struct omnia_mcu {
 	u16 last_status;
 	struct delayed_work button_release_emul_work;
 	bool button_pressed_emul;
+
+	/* RTC device for configuring wake-up */
+	struct rtc_device *rtcdev;
+	u32 rtc_alarm;
+	bool front_button_poweron;
 };
 
 static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
@@ -39,6 +46,17 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
 	return ret < 0 ? ret : 0;
 }
 
+static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
+				      u32 val)
+{
+	u8 buf[5];
+
+	buf[0] = cmd;
+	put_unaligned_le32(val, &buf[1]);
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
 static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd, void *reply,
 				 unsigned int len)
 {
@@ -117,5 +135,6 @@ static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
 
 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
 void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu);
+int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
 
 #endif /* __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H */
author maintainer
#26 lore raw

PATCH v2 5/7 platform: cznic: turris-omnia-mcu: Add support for MCU watchdog

In reply to Marek Behún

Add support for the watchdog mechanism provided by the MCU.

---
 drivers/platform/cznic/Kconfig                |   2 +
 drivers/platform/cznic/Makefile               |   1 +
 .../platform/cznic/turris-omnia-mcu-base.c    |   4 +
 .../cznic/turris-omnia-mcu-watchdog.c         | 126 ++++++++++++++++++
 drivers/platform/cznic/turris-omnia-mcu.h     |  24 ++++
 5 files changed, 157 insertions(+)
 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c
drivers/platform/cznic/Kconfig +2 -0
index 0a752aa654fa..e2649cdecc38 100644
@@ -20,6 +20,7 @@ config TURRIS_OMNIA_MCU
 	select GPIOLIB
 	select GPIOLIB_IRQCHIP
 	select RTC_CLASS
+	select WATCHDOG_CORE
 	help
 	  Say Y here to add support for the features implemented by the
 	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
@@ -27,6 +28,7 @@ config TURRIS_OMNIA_MCU
 	  - board poweroff into true low power mode (with voltage regulators
 	    disabled) and the ability to configure wake up from this mode (via
 	    rtcwake)
+	  - MCU watchdog
 	  - GPIO pins
 	    - to get front button press events (the front button can be
 	      configured either to generate press events to the CPU or to change
drivers/platform/cznic/Makefile +1 -0
index 6f1470d1f673..a43997a12d74 100644
@@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
 turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
 turris-omnia-mcu-objs		+= turris-omnia-mcu-gpio.o
 turris-omnia-mcu-objs		+= turris-omnia-mcu-sys-off-wakeup.o
+turris-omnia-mcu-objs		+= turris-omnia-mcu-watchdog.o
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
index b507f81ebe0a..708265203cc1 100644
@@ -230,6 +230,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
 	if (ret)
 		return ret;
 
+	ret = omnia_mcu_register_watchdog(mcu);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file +126 -0

126 lines not on this page · raw message

drivers/platform/cznic/turris-omnia-mcu.h +24 -0
index c40ac07be5f5..89fe39f4255f 100644
@@ -16,6 +16,7 @@
 #include <linux/mutex.h>
 #include <linux/rtc.h>
 #include <linux/turris-omnia-mcu-interface.h>
+#include <linux/watchdog.h>
 #include <linux/workqueue.h>
 
 struct omnia_mcu {
@@ -36,6 +37,9 @@ struct omnia_mcu {
 	struct rtc_device *rtcdev;
 	u32 rtc_alarm;
 	bool front_button_poweron;
+
+	/* MCU watchdog */
+	struct watchdog_device wdt;
 };
 
 static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
@@ -46,6 +50,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
 	return ret < 0 ? ret : 0;
 }
 
+static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd,
+				     u8 val)
+{
+	u8 buf[2] = { cmd, val };
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
+static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd,
+				      u16 val)
+{
+	u8 buf[3];
+
+	buf[0] = cmd;
+	put_unaligned_le16(val, &buf[1]);
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
 static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
 				      u32 val)
 {
@@ -136,5 +159,6 @@ static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
 void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu);
 int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
+int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);
 
 #endif /* __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H */
Signature · 1 line
2.41.0
maintainer
#27 lore raw

Re: [PATCH v2 5/7] platform: cznic: turris-omnia-mcu: Add support for MCU watchdog

Quoted 84 lines · Marek Behún

Add support for the watchdog mechanism provided by the MCU.

---
  drivers/platform/cznic/Kconfig                |   2 +
  drivers/platform/cznic/Makefile               |   1 +
  .../platform/cznic/turris-omnia-mcu-base.c    |   4 +
  .../cznic/turris-omnia-mcu-watchdog.c         | 126 ++++++++++++++++++
  drivers/platform/cznic/turris-omnia-mcu.h     |  24 ++++
  5 files changed, 157 insertions(+)

create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c

drivers/platform/cznic/Kconfig +2 -0
index 0a752aa654fa..e2649cdecc38 100644
@@ -20,6 +20,7 @@ config TURRIS_OMNIA_MCU
  	select GPIOLIB
  	select GPIOLIB_IRQCHIP
  	select RTC_CLASS
+	select WATCHDOG_CORE
  	help
  	  Say Y here to add support for the features implemented by the
  	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
@@ -27,6 +28,7 @@ config TURRIS_OMNIA_MCU
  	  - board poweroff into true low power mode (with voltage regulators
  	    disabled) and the ability to configure wake up from this mode (via
  	    rtcwake)
+	  - MCU watchdog
  	  - GPIO pins
  	    - to get front button press events (the front button can be
  	      configured either to generate press events to the CPU or to change
drivers/platform/cznic/Makefile +1 -0
index 6f1470d1f673..a43997a12d74 100644
@@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU)	+= turris-omnia-mcu.o
  turris-omnia-mcu-objs		:= turris-omnia-mcu-base.o
  turris-omnia-mcu-objs		+= turris-omnia-mcu-gpio.o
  turris-omnia-mcu-objs		+= turris-omnia-mcu-sys-off-wakeup.o
+turris-omnia-mcu-objs		+= turris-omnia-mcu-watchdog.o
drivers/platform/cznic/turris-omnia-mcu-base.c +4 -0
index b507f81ebe0a..708265203cc1 100644
@@ -230,6 +230,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
  	if (ret)
  		return ret;
 
+	ret = omnia_mcu_register_watchdog(mcu);
+	if (ret)
+		return ret;
+
  	return 0;
  }
 
drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file +22 -0
new file mode 100644
index 000000000000..2238b2167c45
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CZ.NIC's Turris Omnia MCU watchdog driver
+ *
+ * 2023 by Marek Behún <[email protected]>
+ */
+
+#include "turris-omnia-mcu.h"
+
+#define WATCHDOG_TIMEOUT		120
+
+static unsigned int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+			   __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int omnia_wdt_start(struct watchdog_device *wdt)
+{

drivers/platform/cznic/turris-omnia-mcu-watchdog.c line 32

Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file +10 -0
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+	/*
+	 * The MCU also implements LED controller, and the LED driver may in
+	 * some cases require more I2C bandwidth. In order to prevent stealing
+	 * that bandwidth from the LED driver, configure minimum time between
+	 * pings to 10% of the configured timeout (for 120 second timeout we
+	 * will be pinging at most every 12 seconds).
+	 */
+	mcu->wdt.min_hw_heartbeat_ms = 100 * mcu->wdt.timeout;

This is not what min_hw_heartbeat_ms is supposed to be used for, and it is an abuse of the API which is not guaranteed to work. The value is exopected to be set at registration time, and it is not supposed to change dynamically. It is also competely unnecessary since no sane watchdog daemon will ping at such a high rate. Please drop.

Guenter

Quoted 149 lines
drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file +94 -0
+
+	return omnia_cmd_write_u8(mcu->client, CMD_SET_WATCHDOG_STATE, 1);
+}
+
+static int omnia_wdt_stop(struct watchdog_device *wdt)
+{
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+	return omnia_cmd_write_u8(mcu->client, CMD_SET_WATCHDOG_STATE, 0);
+}
+
+static int omnia_wdt_ping(struct watchdog_device *wdt)
+{
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+	return omnia_cmd_write_u8(mcu->client, CMD_SET_WATCHDOG_STATE, 1);
+}
+
+static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
+				 unsigned int timeout)
+{
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+	return omnia_cmd_write_u16(mcu->client, CMD_SET_WDT_TIMEOUT,
+				   timeout * 10);
+}
+
+static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+	int ret;
+
+	ret = omnia_cmd_read_u16(mcu->client, CMD_GET_WDT_TIMELEFT);
+	if (ret < 0) {
+		dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
+			ret);
+		return 0;
+	}
+
+	return ret / 10;
+}
+
+static const struct watchdog_info omnia_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+	.identity = "Turris Omnia MCU Watchdog",
+};
+
+static const struct watchdog_ops omnia_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= omnia_wdt_start,
+	.stop		= omnia_wdt_stop,
+	.ping		= omnia_wdt_ping,
+	.set_timeout	= omnia_wdt_set_timeout,
+	.get_timeleft	= omnia_wdt_get_timeleft,
+};
+
+int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
+{
+	struct device *dev = &mcu->client->dev;
+	int ret;
+
+	if (!(mcu->features & FEAT_WDT_PING))
+		return 0;
+
+	mcu->wdt.info = &omnia_wdt_info;
+	mcu->wdt.ops = &omnia_wdt_ops;
+	mcu->wdt.parent = dev;
+	mcu->wdt.min_timeout = 1;
+	mcu->wdt.max_timeout = 6553; /* 65535 deciseconds */
+
+	mcu->wdt.timeout = WATCHDOG_TIMEOUT;
+	watchdog_init_timeout(&mcu->wdt, timeout, dev);
+
+	watchdog_set_drvdata(&mcu->wdt, mcu);
+
+	omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);
+
+	ret = omnia_cmd_read_u8(mcu->client, CMD_GET_WATCHDOG_STATE);
+	if (ret < 0) {
+		dev_err(dev, "Cannot get MCU watchdog state: %d\n", ret);
+		return ret;
+	}
+
+	if (ret)
+		set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);
+
+	watchdog_set_nowayout(&mcu->wdt, nowayout);
+	watchdog_stop_on_reboot(&mcu->wdt);
+	ret = devm_watchdog_register_device(dev, &mcu->wdt);
+	if (ret)
+		dev_err(dev, "Cannot register MCU watchdog: %d\n", ret);
+
+	return ret;
+}
drivers/platform/cznic/turris-omnia-mcu.h +24 -0
index c40ac07be5f5..89fe39f4255f 100644
@@ -16,6 +16,7 @@
  #include <linux/mutex.h>
  #include <linux/rtc.h>
  #include <linux/turris-omnia-mcu-interface.h>
+#include <linux/watchdog.h>
  #include <linux/workqueue.h>
 
  struct omnia_mcu {
@@ -36,6 +37,9 @@ struct omnia_mcu {
  	struct rtc_device *rtcdev;
  	u32 rtc_alarm;
  	bool front_button_poweron;
+
+	/* MCU watchdog */
+	struct watchdog_device wdt;
  };
 
  static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
@@ -46,6 +50,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
  	return ret < 0 ? ret : 0;
  }
 
+static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd,
+				     u8 val)
+{
+	u8 buf[2] = { cmd, val };
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
+static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd,
+				      u16 val)
+{
+	u8 buf[3];
+
+	buf[0] = cmd;
+	put_unaligned_le16(val, &buf[1]);
+
+	return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
  static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
  				      u32 val)
  {
@@ -136,5 +159,6 @@ static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
  int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
  void omnia_mcu_unregister_gpiochip(struct omnia_mcu *mcu);
  int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
+int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);
 
  #endif /* __DRIVERS_PLATFORM_CZNIC_TURRIS_OMNIA_MCU_H */
author maintainer
#28 lore raw

Re: [PATCH v2 5/7] platform: cznic: turris-omnia-mcu: Add support for MCU watchdog

Quoted 10 lines
Quoted 10 lines
drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file +10 -0
+static int omnia_wdt_start(struct watchdog_device *wdt)
+{
+	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
+
+	/*
+	 * The MCU also implements LED controller, and the LED driver may in
+	 * some cases require more I2C bandwidth. In order to prevent stealing
+	 * that bandwidth from the LED driver, configure minimum time between
+	 * pings to 10% of the configured timeout (for 120 second timeout we
+	 * will be pinging at most every 12 seconds).
Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+	 */
+	mcu->wdt.min_hw_heartbeat_ms = 100 * mcu->wdt.timeout;  

This is not what min_hw_heartbeat_ms is supposed to be used for, and it is an abuse of the API which is not guaranteed to work. The value is exopected to be set at registration time, and it is not supposed to change dynamically. It is also competely unnecessary since no sane watchdog daemon will ping at such a high rate. Please drop.

Hello Guenter, thanks for the review.

Sorry about this, I thought this was correct cause rti_wdt driver and feared that watchdog daemons would actually ping at such rate.

I will drop it in the next version of the series.

Marek

author maintainer
#29 lore raw

PATCH v2 6/7 ARM: dts: turris-omnia: Add MCU system-controller node

In reply to Marek Behún

Turris Omnia's MCU provides various features that can be configured over I2C at address 0x2a. Add device-tree node.

Fixes 26ca8b52d6e1 ("ARM: dts: add support for Turris Omnia")
---
 .../dts/marvell/armada-385-turris-omnia.dts   | 22 ++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)
arch/arm/boot/dts/marvell/armada-385-turris-omnia.dts +21 -1
index 2d8d319bec83..7682fda4000a 100644
@@ -218,7 +218,22 @@ i2c@0 {
 			#size-cells = <0>;
 			reg = <0>;
 
-			/* STM32F0 command interface at address 0x2a */
+			mcu: system-controller@2a {
+				compatible = "cznic,turris-omnia-mcu";
+				reg = <0x2a>;
+
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcu_pins>;
+
+				interrupt-parent = <&gpio1>;
+				interrupts = <11 IRQ_TYPE_NONE>;
+
+				gpio-controller;
+				#gpio-cells = <2>;
+
+				interrupt-controller;
+				#interrupt-cells = <2>;
+			};
 
 			led-controller@2b {
 				compatible = "cznic,turris-omnia-leds";
@@ -503,6 +518,11 @@ fixed-link {
 };
 
 &pinctrl {
+	mcu_pins: mcu-pins {
+		marvell,pins = "mpp43";
+		marvell,function = "gpio";
+	};
+
 	pcawan_pins: pcawan-pins {
 		marvell,pins = "mpp46";
 		marvell,function = "gpio";
Signature · 1 line
2.41.0
author maintainer
#30 lore raw

PATCH v2 7/7 ARM: dts: turris-omnia: Add GPIO key node for front button

In reply to Marek Behún

Now that we have the MCU device-tree node, which acts as a GPIO controller, add GPIO key node for the front button.

Fixes 26ca8b52d6e1 ("ARM: dts: add support for Turris Omnia")
---
 .../boot/dts/marvell/armada-385-turris-omnia.dts    | 13 +++++++++++++
 1 file changed, 13 insertions(+)
arch/arm/boot/dts/marvell/armada-385-turris-omnia.dts +13 -0
index 7682fda4000a..016adfa0b470 100644
@@ -112,6 +112,19 @@ sfp: sfp {
 		status = "disabled";
 	};
 
+	gpio-keys {
+		compatible = "gpio-keys";
+
+		front-button {
+			label = "Front Button";
+			linux,code = <KEY_VENDOR>;
+			linux,can-disable;
+			gpios = <&mcu 12 GPIO_ACTIVE_HIGH>;
+			/* debouncing is done by the microcontroller */
+			debounce-interval = <0>;
+		};
+	};
+
 	sound {
 		compatible = "simple-audio-card";
 		simple-audio-card,name = "SPDIF";
Signature · 1 line
2.41.0