The Sony Cronos Platform Controller is a multi-purpose platform controller
that provides both a watchdog timer and an LED controller for the Sony
Interactive Entertainment Cronos x86 server platform. As both functions
are provided by the same CPLD, a multi-function device is exposed as the
parent of both functions.
Add a DT binding for this device.
Signed-off-by: Shawn Anastasio <sanastasio@raptorengineering.com>
Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
---
.../bindings/mfd/sony,cronos-smc.yaml | 113 ++++++++++++++++++
1 file changed, 113 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
diff --git a/Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml b/Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
new file mode 100644
index 000000000000..ec0fd3ed6073
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright 2025 Raptor Engineering, LLC
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/sony,cronos-smc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony Cronos Platform Controller CPLD multi-function device
+
+maintainers:
+ - Georgy Yakovlev <Georgy.Yakovlev@sony.com>
+
+description:
+ The Sony Cronos Platform Controller CPLD is a multi-purpose platform
+ controller that provides both a watchdog timer and an LED controller for the
+ Sony Interactive Entertainment Cronos x86 server platform. As both functions
+ are provided by the same CPLD, a multi-function device is exposed as the
+ parent of both functions.
+
+properties:
+ compatible:
+ const: sony,cronos-smc
+
+ reg:
+ maxItems: 1
+
+ timeout-sec: true
+
+ leds:
+ type: object
+ additionalProperties: false
+ description: |
+ The Cronos LED controller is a subfunction of the Cronos platform
+ controller, which is a multi-function device.
+
+ Each led is represented as a child node of sony,cronos-led. Fifteen RGB
+ LEDs are supported by the platform.
+
+ properties:
+ compatible:
+ const: sony,cronos-led
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ patternProperties:
+ "^multi-led@[0-15]$":
+ type: object
+ $ref: leds-class-multicolor.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ description:
+ LED channel number (0..15)
+ minimum: 0
+ maximum: 15
+
+ required:
+ - reg
+
+ required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: /schemas/watchdog/watchdog.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ smc@3f {
+ compatible = "sony,cronos-smc";
+ reg = <0x3f>;
+
+ timeout-sec = <20>;
+
+ leds {
+ compatible = "sony,cronos-led";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ multi-led@0 {
+ /*
+ * No subnodes are needed, this controller only supports RGB
+ * LEDs.
+ */
+ reg = <0>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_STATUS;
+ };
+ };
+ };
+ };
--
2.39.5
On 20/10/2025 20:12, Timothy Pearson wrote:
> +
> +properties:
> + compatible:
> + const: sony,cronos-smc
> +
> + reg:
> + maxItems: 1
> +
> + timeout-sec: true
Drop, not needed.
> +
> + leds:
> + type: object
> + additionalProperties: false
> + description: |
> + The Cronos LED controller is a subfunction of the Cronos platform
> + controller, which is a multi-function device.
> +
> + Each led is represented as a child node of sony,cronos-led. Fifteen RGB
> + LEDs are supported by the platform.
Fifteen?
> +
> + properties:
> + compatible:
> + const: sony,cronos-led
> +
> + reg:
> + maxItems: 1
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + patternProperties:
> + "^multi-led@[0-15]$":
Unit addresses are hex.
> + type: object
> + $ref: leds-class-multicolor.yaml#
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + description:
> + LED channel number (0..15)
but here is sixteen...
> + minimum: 0
> + maximum: 15
> +
> + required:
> + - reg
> +
> + required:
> + - compatible
> + - "#address-cells"
> + - "#size-cells"
> +
> +required:
> + - compatible
> + - reg
> +
> +allOf:
> + - $ref: /schemas/watchdog/watchdog.yaml
Come with sensible, generic node name and update the schema like I did.
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> +
> + #include <dt-bindings/leds/common.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + smc@3f {
> + compatible = "sony,cronos-smc";
> + reg = <0x3f>;
> +
> + timeout-sec = <20>;
> +
> + leds {
> + compatible = "sony,cronos-led";
Keep consistent indentation. Use 4 spaces for example indentation.
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + multi-led@0 {
> + /*
> + * No subnodes are needed, this controller only supports RGB
> + * LEDs.
> + */
> + reg = <0>;
> + color = <LED_COLOR_ID_MULTI>;
> + function = LED_FUNCTION_STATUS;
> + };
> + };
> + };
> + };
Best regards,
Krzysztof
On Mon, 20 Oct 2025 13:12:01 -0500, Timothy Pearson wrote:
> The Sony Cronos Platform Controller is a multi-purpose platform controller
> that provides both a watchdog timer and an LED controller for the Sony
> Interactive Entertainment Cronos x86 server platform. As both functions
> are provided by the same CPLD, a multi-function device is exposed as the
> parent of both functions.
>
> Add a DT binding for this device.
>
> Signed-off-by: Shawn Anastasio <sanastasio@raptorengineering.com>
> Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
> ---
> .../bindings/mfd/sony,cronos-smc.yaml | 113 ++++++++++++++++++
> 1 file changed, 113 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml:
Error in referenced schema matching $id: http://devicetree.org/schemas/mfd/leds-class-multicolor.yaml
Tried these paths (check schema $id if path is wrong):
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/leds-class-multicolor.yaml
/usr/local/lib/python3.13/dist-packages/dtschema/schemas/mfd/leds-class-multicolor.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/sony,cronos-smc.example.dtb: smc@3f (sony,cronos-smc): $nodename:0: 'smc@3f' does not match '^(timer|watchdog)(@.*|-([0-9]|[1-9][0-9]+))?$'
from schema $id: http://devicetree.org/schemas/mfd/sony,cronos-smc.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/sony,cronos-smc.example.dtb: smc@3f (sony,cronos-smc): leds:multi-led@0: {'reg': [[0]], 'color': 8, 'function': ['status']} should not be valid under {'description': "Can't find referenced schema: http://devicetree.org/schemas/mfd/leds-class-multicolor.yaml#"}
from schema $id: http://devicetree.org/schemas/mfd/sony,cronos-smc.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/sony,cronos-smc.example.dtb: smc@3f (sony,cronos-smc): leds:multi-led@0: Unevaluated properties are not allowed ('color', 'function' were unexpected)
from schema $id: http://devicetree.org/schemas/mfd/sony,cronos-smc.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/sony,cronos-smc.example.dtb: smc@3f (sony,cronos-smc): Unevaluated properties are not allowed ('leds' was unexpected)
from schema $id: http://devicetree.org/schemas/mfd/sony,cronos-smc.yaml#
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/1587929609.1802041.1760983921227.JavaMail.zimbra@raptorengineeringinc.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
The Sony Cronos Platform Controller is a multi-purpose platform controller
that provides both a watchdog timer and an LED controller for the Sony
Interactive Entertainment Cronos x86 server platform. As both functions
are provided by the same CPLD, a multi-function device is exposed as the
parent of both functions.
Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
Signed-off-by: Shawn Anastasio <sanastasio@raptorengineering.com>
---
MAINTAINERS | 7 ++
drivers/mfd/Kconfig | 11 ++
drivers/mfd/Makefile | 2 +
drivers/mfd/sony-cronos-smc.c | 212 ++++++++++++++++++++++++++++++++
include/linux/mfd/sony-cronos.h | 61 +++++++++
5 files changed, 293 insertions(+)
create mode 100644 drivers/mfd/sony-cronos-smc.c
create mode 100644 include/linux/mfd/sony-cronos.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 545a4776795e..8570b12a3f66 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23881,6 +23881,13 @@ S: Maintained
F: drivers/ssb/
F: include/linux/ssb/
+SONY CRONOS SMC DRIVER
+M: Georgy Yakovlev <Georgy.Yakovlev@sony.com>
+S: Maintained
+F: Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
+F: drivers/mfd/sony-cronos-smc.c
+F: include/linux/mfd/sony-cronos.h
+
SONY IMX208 SENSOR DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
L: linux-media@vger.kernel.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6cec1858947b..1b08f5ae648d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2382,6 +2382,17 @@ config MFD_QCOM_PM8008
under it in the device tree. Additional drivers must be enabled in
order to use the functionality of the device.
+config MFD_SONY_CRONOS_SMC
+ tristate "Sony Cronos System Management Controller"
+ select MFD_CORE
+ select REGMAP_I2C
+ depends on I2C && OF
+ help
+ Support for the Sony Cronos system controller. Additional drivers must
+ be enabled in order to use the functionality of the device, including LED
+ control and the system watchdog. The controller itself is a custom design
+ tailored to the specific needs of the Sony Cronos hardware platform.
+
menu "Multimedia Capabilities Port drivers"
depends on ARCH_SA1100
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 865e9f12faff..99700f423fe7 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -300,6 +300,8 @@ obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o
obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o
obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
+obj-$(CONFIG_MFD_SONY_CRONOS_SMC) += sony-cronos-smc.o
+
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o
diff --git a/drivers/mfd/sony-cronos-smc.c b/drivers/mfd/sony-cronos-smc.c
new file mode 100644
index 000000000000..9d9b5402f89b
--- /dev/null
+++ b/drivers/mfd/sony-cronos-smc.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Device driver for Sony Cronos SMCs
+ * Copyright (C) 2015-2017 Dialog Semiconductor
+ * Copyright (C) 2022-2025 Raptor Engineering, LLC
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/sony-cronos.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+static const struct mfd_cell cronos_smc_devs[] = {
+ {
+ .name = "cronos-watchdog",
+ .of_compatible = "sony,cronos-watchdog",
+ },
+ {
+ .name = "cronos-led",
+ .of_compatible = "sony,cronos-led",
+ },
+};
+
+static int sony_cronos_get_device_type(struct sony_cronos_smc *ddata)
+{
+ int device_id;
+ int byte_high;
+ int byte_low;
+ int ret;
+
+ ret = regmap_read(ddata->regmap, CRONOS_SMC_DEVICE_ID_HIGH_REG, &byte_high);
+ if (ret) {
+ dev_err(ddata->dev, "Cannot read ddata ID high byte.\n");
+ return -EIO;
+ }
+ ret = regmap_read(ddata->regmap, CRONOS_SMC_DEVICE_ID_LOW_REG, &byte_low);
+ if (ret) {
+ dev_err(ddata->dev, "Cannot read ddata ID low byte.\n");
+ return -EIO;
+ }
+
+ device_id = byte_high << 8;
+ device_id |= byte_low;
+
+ if (device_id != CRONOS_SMC_DEVICE_ID) {
+ dev_err(ddata->dev, "Unsupported device ID 0x%04x\n", device_id);
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+static bool cronos_smc_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CRONOS_SMC_BRIGHTNESS_RED_REG:
+ case CRONOS_SMC_BRIGHTNESS_GREEN_REG:
+ case CRONOS_SMC_BRIGHTNESS_BLUE_REG:
+ case CRONOS_LEDS_SMC_STATUS_REG:
+ case CRONOS_LEDS_SWITCH_STATUS_REG:
+ case CRONOS_LEDS_CCM1_STATUS_REG:
+ case CRONOS_LEDS_CCM2_STATUS_REG:
+ case CRONOS_LEDS_CCM3_STATUS_REG:
+ case CRONOS_LEDS_CCM4_STATUS_REG:
+ case CRONOS_LEDS_CCM_POWER_REG:
+
+ case CRONOS_WDT_CTL_REG:
+ case CRONOS_WDT_CLR_REG:
+
+ case CRONOS_SMC_UART_MUX_REG:
+ case CRONOS_SMC_SWITCH_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_SWITCH_RESET_CMD_REG:
+ case CRONOS_SMC_BMC_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_PAYLOAD_POWER_CTL_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cronos_smc_is_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CRONOS_SMC_REVISION_HIGH_REG:
+ case CRONOS_SMC_REVISION_LOW_REG:
+ case CRONOS_SMC_DEVICE_ID_HIGH_REG:
+ case CRONOS_SMC_DEVICE_ID_LOW_REG:
+
+ case CRONOS_SMC_BRIGHTNESS_RED_REG:
+ case CRONOS_SMC_BRIGHTNESS_GREEN_REG:
+ case CRONOS_SMC_BRIGHTNESS_BLUE_REG:
+ case CRONOS_LEDS_SMC_STATUS_REG:
+ case CRONOS_LEDS_SWITCH_STATUS_REG:
+ case CRONOS_LEDS_CCM1_STATUS_REG:
+ case CRONOS_LEDS_CCM2_STATUS_REG:
+ case CRONOS_LEDS_CCM3_STATUS_REG:
+ case CRONOS_LEDS_CCM4_STATUS_REG:
+ case CRONOS_LEDS_CCM_POWER_REG:
+
+ case CRONOS_WDT_CTL_REG:
+ case CRONOS_WDT_CLR_REG:
+
+ case CRONOS_SMC_STATUS_2_REG:
+ case CRONOS_SMC_UART_MUX_REG:
+ case CRONOS_SMC_SWITCH_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_SWITCH_RESET_CMD_REG:
+ case CRONOS_SMC_BMC_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_PAYLOAD_POWER_CTL_REG:
+
+ case CRONOS_SMC_BMC_MAC_LOW_REG ... CRONOS_SMC_BMC_MAC_HIGH_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cronos_smc_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CRONOS_SMC_REVISION_HIGH_REG:
+ case CRONOS_SMC_REVISION_LOW_REG:
+
+ case CRONOS_SMC_SWITCH_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_SWITCH_RESET_CMD_REG:
+ case CRONOS_SMC_BMC_BOOT_FLASH_SELECT_REG:
+ case CRONOS_SMC_PAYLOAD_POWER_CTL_REG:
+
+ case CRONOS_WDT_CTL_REG:
+ case CRONOS_WDT_CLR_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct regmap_config cronos_smc_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = CRONOS_SMC_REVISION_HIGH_REG,
+ .writeable_reg = cronos_smc_is_writeable_reg,
+ .readable_reg = cronos_smc_is_readable_reg,
+ .volatile_reg = cronos_smc_is_volatile_reg,
+ .use_single_read = true,
+ .use_single_write = true,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static const struct of_device_id cronos_smc_dt_ids[] = {
+ {
+ .compatible = "sony,cronos-smc",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cronos_smc_dt_ids);
+
+static int sony_cronos_i2c_probe(struct i2c_client *i2c)
+{
+ struct sony_cronos_smc *ddata;
+ int ret;
+
+ ddata = devm_kzalloc(&i2c->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, ddata);
+ ddata->dev = &i2c->dev;
+
+ ddata->regmap = devm_regmap_init_i2c(i2c, &cronos_smc_regmap_config);
+ if (IS_ERR(ddata->regmap)) {
+ return dev_err_probe(ddata->dev, PTR_ERR(ddata->regmap),
+ "Failed to allocate register map\n");
+ }
+
+ ret = sony_cronos_get_device_type(ddata);
+ if (ret)
+ return ret;
+
+ ret = mfd_add_devices(ddata->dev, PLATFORM_DEVID_AUTO, cronos_smc_devs,
+ ARRAY_SIZE(cronos_smc_devs), NULL, 0, NULL);
+ if (ret) {
+ dev_err(ddata->dev, "Failed to register child devices\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void sony_cronos_i2c_remove(struct i2c_client *i2c)
+{
+ struct sony_cronos_smc *ddata = i2c_get_clientdata(i2c);
+
+ mfd_remove_devices(ddata->dev);
+}
+
+static struct i2c_driver sony_cronos_i2c_driver = {
+ .driver = {
+ .name = "sony-cronos-smc",
+ .of_match_table = of_match_ptr(cronos_smc_dt_ids),
+ },
+ .probe = sony_cronos_i2c_probe,
+ .remove = sony_cronos_i2c_remove,
+};
+module_i2c_driver(sony_cronos_i2c_driver);
+
+MODULE_DESCRIPTION("Device driver for the Sony Cronos system management controller");
+MODULE_AUTHOR("Raptor Engineering, LLC <tpearson@raptorengineering.com>");
+MODULE_LICENSE("GPL");
\ No newline at end of file
diff --git a/include/linux/mfd/sony-cronos.h b/include/linux/mfd/sony-cronos.h
new file mode 100644
index 000000000000..d82e46176bf7
--- /dev/null
+++ b/include/linux/mfd/sony-cronos.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015-2017 Dialog Semiconductor
+ * Copyright (C) 2022 Raptor Engineering, LLC
+ */
+
+#ifndef __MFD_SONY_CRONOS_H__
+#define __MFD_SONY_CRONOS_H__
+
+#define CRONOS_SMC_DEVICE_ID 0x0134
+
+#define CRONOS_SMC_SWITCH_BOOT_FLASH_SELECT_REG 0x00
+#define CRONOS_SMC_SWITCH_RESET_CMD_REG 0x01
+#define CRONOS_SMC_BMC_BOOT_FLASH_SELECT_REG 0x02
+#define CRONOS_BMC_RESET_REG 0x03
+#define CRONOS_WDT_CLR_REG 0x03
+#define CRONOS_SMC_STATUS_2_REG 0x05
+#define CRONOS_SMC_PAYLOAD_POWER_CTL_REG 0x0a
+#define CRONOS_WDT_CTL_REG 0x0c
+#define CRONOS_SMC_UART_MUX_REG 0x0e
+
+#define CRONOS_SMC_BRIGHTNESS_RED_REG 0x17
+#define CRONOS_SMC_BRIGHTNESS_GREEN_REG 0x18
+#define CRONOS_SMC_BRIGHTNESS_BLUE_REG 0x19
+
+#define CRONOS_LEDS_SMC_STATUS_REG 0x10
+#define CRONOS_LEDS_SWITCH_STATUS_REG 0x11
+
+#define CRONOS_LEDS_CCM3_STATUS_REG 0x12
+#define CRONOS_LEDS_CCM2_STATUS_REG 0x13
+#define CRONOS_LEDS_CCM4_STATUS_REG 0x14
+#define CRONOS_LEDS_CCM1_STATUS_REG 0x15
+
+#define CRONOS_LEDS_CCM_POWER_REG 0x16
+
+#define CRONOS_SMC_BMC_MAC_LOW_REG 0x30
+#define CRONOS_SMC_BMC_MAC_HIGH_REG 0x35
+
+#define CRONOS_SMC_DEVICE_ID_LOW_REG 0x70
+#define CRONOS_SMC_DEVICE_ID_HIGH_REG 0x71
+#define CRONOS_SMC_REVISION_LOW_REG 0x72
+#define CRONOS_SMC_REVISION_HIGH_REG 0x73
+
+#define CRONOS_SMC_LEDS_BRIGHTNESS_SET_MASK 0x7F
+#define CRONOS_LEDS_MAX_BRIGHTNESS 0x7F
+
+#define CRONOS_BMC_RESET_VAL 0xc2
+
+#define CRONOS_WDT_CLR_VAL 0xc3
+#define CRONOS_WDT_ENABLE_MASK 0x80
+#define CRONOS_WDT_ENABLE_VAL 0x80
+#define CRONOS_WDT_DISABLE_VAL 0x00
+#define CRONOS_WDT_TIMEOUT_MASK 0x07
+#define CRONOS_WDT_CTL_RESET_VAL 0x00
+
+struct sony_cronos_smc {
+ struct device *dev;
+ struct regmap *regmap;
+};
+
+#endif /* __MFD_SONY_CRONOS_H__ */
--
2.39.5
On 20/10/2025 20:14, Timothy Pearson wrote:
> The Sony Cronos Platform Controller is a multi-purpose platform controller
> that provides both a watchdog timer and an LED controller for the Sony
> Interactive Entertainment Cronos x86 server platform. As both functions
> are provided by the same CPLD, a multi-function device is exposed as the
> parent of both functions.
>
> Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
> Signed-off-by: Shawn Anastasio <sanastasio@raptorengineering.com>
Your threading is completely broken. git format-patch -v4 -4 --cover-letter.
Or just use b4.
https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
> ---
> MAINTAINERS | 7 ++
> drivers/mfd/Kconfig | 11 ++
> drivers/mfd/Makefile | 2 +
> drivers/mfd/sony-cronos-smc.c | 212 ++++++++++++++++++++++++++++++++
> include/linux/mfd/sony-cronos.h | 61 +++++++++
> 5 files changed, 293 insertions(+)
> create mode 100644 drivers/mfd/sony-cronos-smc.c
> create mode 100644 include/linux/mfd/sony-cronos.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 545a4776795e..8570b12a3f66 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23881,6 +23881,13 @@ S: Maintained
> F: drivers/ssb/
> F: include/linux/ssb/
>
> +SONY CRONOS SMC DRIVER
> +M: Georgy Yakovlev <Georgy.Yakovlev@sony.com>
> +S: Maintained
> +F: Documentation/devicetree/bindings/mfd/sony,cronos-smc.yaml
> +F: drivers/mfd/sony-cronos-smc.c
> +F: include/linux/mfd/sony-cronos.h
> +
> SONY IMX208 SENSOR DRIVER
> M: Sakari Ailus <sakari.ailus@linux.intel.com>
> L: linux-media@vger.kernel.org
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 6cec1858947b..1b08f5ae648d 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2382,6 +2382,17 @@ config MFD_QCOM_PM8008
> under it in the device tree. Additional drivers must be enabled in
> order to use the functionality of the device.
>
> +config MFD_SONY_CRONOS_SMC
> + tristate "Sony Cronos System Management Controller"
> + select MFD_CORE
> + select REGMAP_I2C
> + depends on I2C && OF
> + help
> + Support for the Sony Cronos system controller. Additional drivers must
> + be enabled in order to use the functionality of the device, including LED
> + control and the system watchdog. The controller itself is a custom design
> + tailored to the specific needs of the Sony Cronos hardware platform.
> +
> menu "Multimedia Capabilities Port drivers"
> depends on ARCH_SA1100
>
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 865e9f12faff..99700f423fe7 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -300,6 +300,8 @@ obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o
> obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o
> obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
>
> +obj-$(CONFIG_MFD_SONY_CRONOS_SMC) += sony-cronos-smc.o
> +
> obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
>
> obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o
> diff --git a/drivers/mfd/sony-cronos-smc.c b/drivers/mfd/sony-cronos-smc.c
> new file mode 100644
> index 000000000000..9d9b5402f89b
> --- /dev/null
> +++ b/drivers/mfd/sony-cronos-smc.c
> @@ -0,0 +1,212 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Device driver for Sony Cronos SMCs
> + * Copyright (C) 2015-2017 Dialog Semiconductor
> + * Copyright (C) 2022-2025 Raptor Engineering, LLC
> + */
> +
> +#include <linux/device.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/sony-cronos.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +static const struct mfd_cell cronos_smc_devs[] = {
> + {
> + .name = "cronos-watchdog",
> + .of_compatible = "sony,cronos-watchdog",
Undocumented compatible drop.
Best regards,
Krzysztof
The Sony Cronos Platform Controller is a multi-purpose platform controller with
an integrated multi-channel RGB LED controller. The LED controller is a
pseudo-RGB device with only two states for each of the RGB subcomponents of
each LED, but is exposed as a full RGB device for ease of integration with
userspace software. Internal thresholding is used to convert the color values
to the required on/off RGB subcomponent controls.
Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
---
drivers/leds/Kconfig | 19 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-sony-cronos.c | 378 ++++++++++++++++++++++++++++++++
3 files changed, 398 insertions(+)
create mode 100644 drivers/leds/leds-sony-cronos.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 06e6291be11b..b5a7c2145dd0 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1013,6 +1013,25 @@ config LEDS_IP30
To compile this driver as a module, choose M here: the module
will be called leds-ip30.
+config LEDS_SONY_CRONOS
+ tristate "LED support for the Sony Cronos SMC"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on LEDS_CLASS && I2C
+ depends on LEDS_CLASS_MULTICOLOR
+ depends on MFD_SONY_CRONOS_SMC
+
+ help
+ Say Y here to include support for LEDs for the
+ Sony Cronos system management controller.
+
+ All known Cronos systems use the ASpeed AST2600 SoC,
+ therefore the configuration option is gated on
+ ARCH_ASPEED selection. If this changes, add the new
+ SoCs to the selection list.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-sony-cronos.
+
config LEDS_ACER_A500
tristate "Power button LED support for Acer Iconia Tab A500"
depends on LEDS_CLASS && MFD_ACER_A500_EC
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 9a0333ec1a86..6dbcf747cab6 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -84,6 +84,7 @@ obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
+obj-$(CONFIG_LEDS_SONY_CRONOS) += leds-sony-cronos.o
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
diff --git a/drivers/leds/leds-sony-cronos.c b/drivers/leds/leds-sony-cronos.c
new file mode 100644
index 000000000000..ce71a8b6ce94
--- /dev/null
+++ b/drivers/leds/leds-sony-cronos.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED driver for Sony Cronos SMCs
+ * Copyright (C) 2012 Dialog Semiconductor Ltd.
+ * Copyright (C) 2023 Sony Interactive Entertainment
+ * Copyright (C) 2025 Raptor Engineering, LLC
+ */
+
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mfd/sony-cronos.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* Masks and Bit shifts */
+#define CRONOS_LEDS_STATUS_FLASHING_MASK 0x40
+#define CRONOS_LEDS_STATUS_FLASHING_SHIFT 6
+#define CRONOS_LEDS_STATUS_COLOR_MASK 0x07
+#define CRONOS_LEDS_STATUS_COLOR_SHIFT 0
+
+#define CRONOS_LEDS_LINK_FLASHING_MASK 0x80
+#define CRONOS_LEDS_LINK_FLASHING_SHIFT 7
+#define CRONOS_LEDS_LINK_COLOR_MASK 0x38
+#define CRONOS_LEDS_LINK_COLOR_SHIFT 3
+
+#define CRONOS_LEDS_CCM1_POWER_COLOR_MASK 0x03
+#define CRONOS_LEDS_CCM1_POWER_COLOR_SHIFT 0
+#define CRONOS_LEDS_CCM2_POWER_COLOR_MASK 0x0C
+#define CRONOS_LEDS_CCM2_POWER_COLOR_SHIFT 2
+#define CRONOS_LEDS_CCM3_POWER_COLOR_MASK 0x30
+#define CRONOS_LEDS_CCM3_POWER_COLOR_SHIFT 4
+#define CRONOS_LEDS_CCM4_POWER_COLOR_MASK 0xC0
+#define CRONOS_LEDS_CCM4_POWER_COLOR_SHIFT 6
+
+/* LED Color mapping - Links and status LEDs */
+#define LED_COLOR_OFF 0x00
+#define LED_COLOR_BLUE 0x01
+#define LED_COLOR_GREEN 0x02
+#define LED_COLOR_RED 0x04
+
+/* LED Color mapping - Power state LEDs */
+#define LED_COLOR_POWER_OFF 0x00
+#define LED_COLOR_POWER_RED 0x02
+#define LED_COLOR_POWER_GREEN 0x01
+
+/* Number of LEDs per type */
+#define LED_COUNT_STATUS 6
+#define LED_COUNT_LINK 5
+#define LED_COUNT_POWER 4
+#define LED_COUNT_ALL (LED_COUNT_STATUS + LED_COUNT_LINK + LED_COUNT_POWER)
+
+enum sony_cronos_led_id {
+ LED_ID_CCM1_STATUS = 0x00,
+ LED_ID_CCM2_STATUS,
+ LED_ID_CCM3_STATUS,
+ LED_ID_CCM4_STATUS,
+ LED_ID_SWITCH_STATUS,
+ LED_ID_SMC_STATUS,
+
+ LED_ID_CCM1_LINK,
+ LED_ID_CCM2_LINK,
+ LED_ID_CCM3_LINK,
+ LED_ID_CCM4_LINK,
+ LED_ID_SWITCH_LINK,
+
+ LED_ID_CCM1_POWER,
+ LED_ID_CCM2_POWER,
+ LED_ID_CCM3_POWER,
+ LED_ID_CCM4_POWER,
+
+ LED_ID_COUNT,
+};
+
+enum sony_cronos_led_type {
+ LED_TYPE_STATUS,
+ LED_TYPE_LINK,
+ LED_TYPE_POWER,
+};
+
+/**
+ * struct sony_cronos_led - per-LED part of driver private data structure
+ * @mc_cdev: multi-color LED class device
+ * @subled_info: per-channel information
+ * @led_register: led register in the MFD regmap
+ * @led_type: sie_cronos_led_type
+ * @led_id: sie_cronos_led_id
+ */
+struct sony_cronos_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[LED_COUNT_ALL];
+ u8 led_register;
+ enum sony_cronos_led_type led_type;
+ enum sony_cronos_led_id led_id;
+};
+
+#define to_cronos_led(l) container_of(l, struct sony_cronos_led, mc_cdev)
+
+/**
+ * struct sony_cronos_leds - driver private data structure
+ * @hw: handle to hw device
+ * @leds: flexible array of per-LED data
+ */
+struct sony_cronos_leds {
+ struct sony_cronos_smc *hw;
+ struct sony_cronos_led leds[];
+};
+
+static int cronos_led_color_store(struct sony_cronos_smc *chip, struct sony_cronos_led *led)
+{
+ u8 byte;
+ u8 color_mask;
+ u8 color_shift;
+ u8 color_key_red;
+ u8 color_key_green;
+ u8 color_key_blue;
+ int ret;
+
+ if (led->led_type == LED_TYPE_STATUS) {
+ color_mask = CRONOS_LEDS_STATUS_COLOR_MASK;
+ color_shift = CRONOS_LEDS_STATUS_COLOR_SHIFT;
+ } else if (led->led_type == LED_TYPE_LINK) {
+ color_mask = CRONOS_LEDS_LINK_COLOR_MASK;
+ color_shift = CRONOS_LEDS_LINK_COLOR_SHIFT;
+ } else if (led->led_id == LED_ID_CCM1_POWER) {
+ color_mask = CRONOS_LEDS_CCM1_POWER_COLOR_MASK;
+ color_shift = CRONOS_LEDS_CCM1_POWER_COLOR_SHIFT;
+ } else if (led->led_id == LED_ID_CCM2_POWER) {
+ color_mask = CRONOS_LEDS_CCM2_POWER_COLOR_MASK;
+ color_shift = CRONOS_LEDS_CCM2_POWER_COLOR_SHIFT;
+ } else if (led->led_id == LED_ID_CCM3_POWER) {
+ color_mask = CRONOS_LEDS_CCM3_POWER_COLOR_MASK;
+ color_shift = CRONOS_LEDS_CCM3_POWER_COLOR_SHIFT;
+ } else if (led->led_id == LED_ID_CCM4_POWER) {
+ color_mask = CRONOS_LEDS_CCM4_POWER_COLOR_MASK;
+ color_shift = CRONOS_LEDS_CCM4_POWER_COLOR_SHIFT;
+ } else
+ return ret;
+
+ switch (led->led_type) {
+ case LED_TYPE_POWER:
+ color_key_red = LED_COLOR_POWER_RED;
+ color_key_green = LED_COLOR_POWER_GREEN;
+ /* Blue channel does not exist for CCM power LEDs */
+ color_key_blue = LED_COLOR_POWER_OFF;
+ break;
+ default:
+ color_key_red = LED_COLOR_RED;
+ color_key_green = LED_COLOR_GREEN;
+ color_key_blue = LED_COLOR_BLUE;
+ }
+
+ /* Assemble SMC color command code */
+ byte = LED_COLOR_POWER_OFF;
+ if (led->subled_info[0].brightness > 128)
+ byte |= color_key_red;
+ if (led->subled_info[1].brightness > 128)
+ byte |= color_key_green;
+ if (led->subled_info[2].brightness > 128)
+ byte |= color_key_blue;
+
+ ret = regmap_update_bits(chip->regmap, led->led_register, color_mask, byte << color_shift);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set color value 0x%02x to LED register 0x%02x", byte,
+ led->led_register);
+ return ret;
+ }
+ return 0;
+}
+
+static ssize_t cronos_led_set_brightness(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct sony_cronos_leds *leds = dev_get_drvdata(cdev->dev->parent);
+ struct sony_cronos_led *led = to_cronos_led(mc_cdev);
+
+ led_mc_calc_color_components(mc_cdev, brightness ?: cdev->max_brightness);
+
+ return cronos_led_color_store(leds->hw, led);
+}
+
+static int sony_cronos_led_register(struct device *dev, struct sony_cronos_leds *leds,
+ struct sony_cronos_led *led, struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ int led_index;
+ int ret, color;
+
+ ret = of_property_read_u32(np, "reg", &led_index);
+ if (ret || led_index >= LED_COUNT_ALL) {
+ dev_err(dev, "'reg' property is out of range (0-%i)\n", LED_COUNT_ALL - 1);
+ return -EINVAL;
+ }
+
+ switch (led_index) {
+ case 0:
+ led->led_register = CRONOS_LEDS_CCM1_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_CCM1_STATUS;
+ break;
+ case 1:
+ led->led_register = CRONOS_LEDS_CCM2_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_CCM2_STATUS;
+ break;
+ case 2:
+ led->led_register = CRONOS_LEDS_CCM3_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_CCM3_STATUS;
+ break;
+ case 3:
+ led->led_register = CRONOS_LEDS_CCM4_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_CCM4_STATUS;
+ break;
+ case 4:
+ led->led_register = CRONOS_LEDS_SWITCH_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_SWITCH_STATUS;
+ break;
+ case 5:
+ led->led_register = CRONOS_LEDS_SMC_STATUS_REG;
+ led->led_type = LED_TYPE_STATUS;
+ led->led_id = LED_ID_SMC_STATUS;
+ break;
+ case 6:
+ led->led_register = CRONOS_LEDS_CCM1_STATUS_REG;
+ led->led_type = LED_TYPE_LINK;
+ led->led_id = LED_ID_CCM1_LINK;
+ break;
+ case 7:
+ led->led_register = CRONOS_LEDS_CCM2_STATUS_REG;
+ led->led_type = LED_TYPE_LINK;
+ led->led_id = LED_ID_CCM1_LINK;
+ break;
+ case 8:
+ led->led_register = CRONOS_LEDS_CCM3_STATUS_REG;
+ led->led_type = LED_TYPE_LINK;
+ led->led_id = LED_ID_CCM2_LINK;
+ break;
+ case 9:
+ led->led_register = CRONOS_LEDS_CCM4_STATUS_REG;
+ led->led_type = LED_TYPE_LINK;
+ led->led_id = LED_ID_CCM3_LINK;
+ break;
+ case 10:
+ led->led_register = CRONOS_LEDS_SWITCH_STATUS_REG;
+ led->led_type = LED_TYPE_LINK;
+ led->led_id = LED_ID_CCM4_LINK;
+ break;
+ case 11:
+ led->led_register = CRONOS_LEDS_CCM_POWER_REG;
+ led->led_type = LED_TYPE_POWER;
+ led->led_id = LED_ID_CCM1_POWER;
+ break;
+ case 12:
+ led->led_register = CRONOS_LEDS_CCM_POWER_REG;
+ led->led_type = LED_TYPE_POWER;
+ led->led_id = LED_ID_CCM2_POWER;
+ break;
+ case 13:
+ led->led_register = CRONOS_LEDS_CCM_POWER_REG;
+ led->led_type = LED_TYPE_POWER;
+ led->led_id = LED_ID_CCM3_POWER;
+ break;
+ case 14:
+ led->led_register = CRONOS_LEDS_CCM_POWER_REG;
+ led->led_type = LED_TYPE_POWER;
+ led->led_id = LED_ID_CCM4_POWER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(np, "color", &color);
+ if (ret || color != LED_COLOR_ID_RGB) {
+ dev_warn(dev,
+ "Node %pOF: must contain 'color' property with value LED_COLOR_ID_RGB\n",
+ np);
+ return -EINVAL;
+ }
+
+ led->subled_info[0].color_index = LED_COLOR_ID_RED;
+ led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+
+ /* Initial color is white */
+ for (int i = 0; i < LED_COUNT_ALL; i++) {
+ led->subled_info[i].intensity = 255;
+ led->subled_info[i].brightness = 255;
+ led->subled_info[i].channel = i;
+ }
+
+ led->mc_cdev.subled_info = led->subled_info;
+ led->mc_cdev.num_colors = LED_COUNT_ALL;
+
+ init_data.fwnode = &np->fwnode;
+
+ cdev = &led->mc_cdev.led_cdev;
+ cdev->max_brightness = 255;
+ cdev->brightness_set_blocking = cronos_led_set_brightness;
+
+ /* Set initial color */
+ ret = cronos_led_color_store(leds->hw, led);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF initial color\n", np);
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot register LED %pOF\n", np);
+
+ /* Set global brightness for all LEDs */
+ ret = regmap_write(leds->hw->regmap, CRONOS_SMC_BRIGHTNESS_RED_REG, 0x00);
+ ret = regmap_write(leds->hw->regmap, CRONOS_SMC_BRIGHTNESS_GREEN_REG, 0x00);
+ ret = regmap_write(leds->hw->regmap, CRONOS_SMC_BRIGHTNESS_BLUE_REG, 0x00);
+
+ return 0;
+}
+
+static int sony_cronos_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct sony_cronos_smc *chip;
+ struct sony_cronos_leds *leds;
+ struct sony_cronos_led *led;
+ int ret, count;
+
+ chip = dev_get_drvdata(dev->parent);
+ if (!chip)
+ return -EINVAL;
+
+ count = of_get_available_child_count(np);
+ if (count == 0)
+ return dev_err_probe(dev, -ENODEV, "LEDs are not defined in device tree!\n");
+ if (count > LED_COUNT_ALL)
+ return dev_err_probe(dev, -EINVAL, "Too many LEDs defined in device tree!\n");
+
+ leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds->hw = chip;
+
+ led = &leds->leds[0];
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = sony_cronos_led_register(dev, leds, led, child);
+ if (ret)
+ return ret;
+
+ led++;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sony_cronos_led_of_id_table[] = {
+ { .compatible = "sie,cronos-led", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sony_cronos_led_of_id_table);
+
+static struct platform_driver sony_cronos_led_driver = {
+ .driver = {
+ .name = "sie-cronos-led",
+ .of_match_table = sony_cronos_led_of_id_table,
+ },
+ .probe = sony_cronos_leds_probe,
+};
+module_platform_driver(sony_cronos_led_driver);
+
+MODULE_DESCRIPTION("LED driver for SIE Cronos SMCs");
+MODULE_AUTHOR("Timothy Pearson <tpearson@raptorengineering.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sony-cronos-leds");
--
2.39.5
The Sony Cronos Platform Controller is a multi-purpose platform controller with
an integrated watchdog. Add the watchdog driver for the Cronos SMC.
Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
---
drivers/watchdog/Kconfig | 17 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/sony-cronos-wdt.c | 283 +++++++++++++++++++++++++++++
3 files changed, 301 insertions(+)
create mode 100644 drivers/watchdog/sony-cronos-wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 05008d937e40..843ee5f8d750 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -416,6 +416,23 @@ config SL28CPLD_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called sl28cpld_wdt.
+config SONY_CRONOS_WATCHDOG
+ tristate "Sony Cronos CPLD Watchdog"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on I2C
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ for the Sony Cronos control CPLD.
+
+ All known Cronos systems use the ASpeed AST2600 SoC,
+ therefore the configuration option is gated on
+ ARCH_ASPEED selection. If this changes, add the new
+ SoCs to the selection list.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sony-cronos-wdt.
+
# ALPHA Architecture
# ARM Architecture
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index b680e4d3c1bc..724d5982c240 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -243,3 +243,4 @@ obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
obj-$(CONFIG_SL28CPLD_WATCHDOG) += sl28cpld_wdt.o
+obj-$(CONFIG_SONY_CRONOS_WATCHDOG) += sony-cronos-wdt.o
diff --git a/drivers/watchdog/sony-cronos-wdt.c b/drivers/watchdog/sony-cronos-wdt.c
new file mode 100644
index 000000000000..650fcee28885
--- /dev/null
+++ b/drivers/watchdog/sony-cronos-wdt.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog device driver for Sony Cronos SMCs
+ * Copyright (C) 2015 Dialog Semiconductor Ltd.
+ * Copyright (C) 2022-2025 Raptor Engineering, LLC
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/sony-cronos.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+static const unsigned int wdt_timeout[] = { 10, 80 };
+static const unsigned int wdt_timeout_ctl_bits[] = { 0x1, 0x0 };
+#define CRONOS_TWDSCALE_DISABLE 0
+#define CRONOS_TWDSCALE_MIN 1
+#define CRONOS_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
+#define CRONOS_WDT_MIN_TIMEOUT wdt_timeout[CRONOS_TWDSCALE_MIN]
+#define CRONOS_WDT_MAX_TIMEOUT wdt_timeout[CRONOS_TWDSCALE_MAX]
+#define CRONOS_WDG_DEFAULT_TIMEOUT wdt_timeout[CRONOS_TWDSCALE_MAX]
+
+struct sony_cronos_watchdog {
+ struct sony_cronos_smc *hw;
+ struct watchdog_device wdtdev;
+};
+
+static unsigned int sony_cronos_wdt_read_timeout(struct sony_cronos_watchdog *wdt)
+{
+ unsigned int i;
+ unsigned int val;
+
+ regmap_read(wdt->hw->regmap, CRONOS_WDT_CTL_REG, &val);
+
+ for (i = CRONOS_TWDSCALE_MIN; i <= CRONOS_TWDSCALE_MAX; i++) {
+ if (wdt_timeout_ctl_bits[i] == (val & CRONOS_WDT_TIMEOUT_MASK))
+ return wdt_timeout[i];
+ }
+
+ dev_err(wdt->hw->dev, "Invalid configuration data present in watchdog control register!\n");
+ return wdt_timeout[CRONOS_WDT_MIN_TIMEOUT];
+}
+
+static unsigned int sony_cronos_wdt_timeout_to_sel(unsigned int secs)
+{
+ unsigned int i;
+
+ for (i = CRONOS_TWDSCALE_MIN; i <= CRONOS_TWDSCALE_MAX; i++) {
+ if (wdt_timeout[i] >= secs)
+ return wdt_timeout_ctl_bits[i];
+ }
+
+ return wdt_timeout_ctl_bits[CRONOS_TWDSCALE_MAX];
+}
+
+static int sony_cronos_reset_watchdog_timer(struct sony_cronos_watchdog *wdt)
+{
+ return regmap_write(wdt->hw->regmap, CRONOS_WDT_CLR_REG, CRONOS_WDT_CLR_VAL);
+}
+
+static int sony_cronos_wdt_update_timeout_register(struct sony_cronos_watchdog *wdt,
+ unsigned int regval)
+{
+ int ret;
+
+ struct sony_cronos_smc *chip = wdt->hw;
+
+ ret = sony_cronos_reset_watchdog_timer(wdt);
+ if (ret) {
+ dev_err(wdt->hw->dev, "Watchdog failed to reset (err = %d)\n", ret);
+ goto done;
+ }
+
+ return regmap_update_bits(chip->regmap, CRONOS_WDT_CTL_REG, CRONOS_WDT_TIMEOUT_MASK,
+ regval);
+
+done:
+ return ret;
+}
+
+static int sony_cronos_wdt_start(struct watchdog_device *wdd)
+{
+ struct sony_cronos_watchdog *wdt = watchdog_get_drvdata(wdd);
+ struct sony_cronos_smc *chip = wdt->hw;
+ unsigned int selector;
+ int ret;
+
+ selector = sony_cronos_wdt_timeout_to_sel(wdt->wdtdev.timeout);
+ ret = sony_cronos_wdt_update_timeout_register(wdt, selector);
+ if (ret) {
+ dev_err(wdt->hw->dev, "Watchdog prestart configuration failed (err = %d)\n", ret);
+ goto done;
+ }
+
+ ret = regmap_update_bits(chip->regmap, CRONOS_WDT_CTL_REG, CRONOS_WDT_ENABLE_MASK, 1);
+
+ if (ret)
+ dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n", ret);
+
+done:
+ return ret;
+}
+
+static int sony_cronos_wdt_stop(struct watchdog_device *wdd)
+{
+ struct sony_cronos_watchdog *wdt = watchdog_get_drvdata(wdd);
+ struct sony_cronos_smc *chip = wdt->hw;
+ int ret;
+
+ ret = regmap_update_bits(chip->regmap, CRONOS_WDT_CTL_REG, CRONOS_WDT_ENABLE_MASK, 1);
+ if (ret)
+ dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n", ret);
+
+ return ret;
+}
+
+static int sony_cronos_wdt_ping(struct watchdog_device *wdd)
+{
+ struct sony_cronos_watchdog *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ /*
+ * Prevent pings from occurring late in system poweroff/reboot sequence
+ * and possibly locking out restart handler from accessing i2c bus.
+ */
+ if (system_state > SYSTEM_RUNNING)
+ return 0;
+
+ ret = sony_cronos_reset_watchdog_timer(wdt);
+ if (ret)
+ dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n", ret);
+
+ return ret;
+}
+
+static int sony_cronos_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ struct sony_cronos_watchdog *wdt = watchdog_get_drvdata(wdd);
+ unsigned int selector;
+ int ret;
+
+ selector = sony_cronos_wdt_timeout_to_sel(timeout);
+ ret = sony_cronos_wdt_update_timeout_register(wdt, selector);
+ if (ret)
+ dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n", ret);
+ else
+ wdd->timeout = wdt_timeout[selector];
+
+ return ret;
+}
+
+static int sony_cronos_wdt_restart(struct watchdog_device *wdd, unsigned long action, void *data)
+{
+ struct sony_cronos_watchdog *wdt = watchdog_get_drvdata(wdd);
+ struct i2c_client *client = to_i2c_client(wdt->hw->dev);
+ int ret;
+
+ /* Don't use regmap because it is not atomic safe */
+ ret = i2c_smbus_write_byte_data(client, CRONOS_WDT_CTL_REG, CRONOS_WDT_CTL_RESET_VAL);
+ ret = i2c_smbus_write_byte_data(client, CRONOS_BMC_RESET_REG, CRONOS_BMC_RESET_VAL);
+ if (ret < 0)
+ dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n", ret);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return ret;
+}
+
+static const struct watchdog_info sony_cronos_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "Sony Cronos WDT",
+};
+
+static const struct watchdog_ops sony_cronos_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = sony_cronos_wdt_start,
+ .stop = sony_cronos_wdt_stop,
+ .ping = sony_cronos_wdt_ping,
+ .set_timeout = sony_cronos_wdt_set_timeout,
+ .restart = sony_cronos_wdt_restart,
+};
+
+static const struct of_device_id sony_cronos_compatible_id_table[] = {
+ {
+ .compatible = "sony,cronos-watchdog",
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, sony_cronos_compatible_id_table);
+
+static int sony_cronos_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int timeout;
+ struct sony_cronos_smc *chip;
+ struct sony_cronos_watchdog *wdt;
+
+ chip = dev_get_drvdata(dev->parent);
+ if (!chip)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->hw = chip;
+
+ wdt->wdtdev.info = &sony_cronos_watchdog_info;
+ wdt->wdtdev.ops = &sony_cronos_watchdog_ops;
+ wdt->wdtdev.min_timeout = CRONOS_WDT_MIN_TIMEOUT;
+ wdt->wdtdev.max_timeout = CRONOS_WDT_MAX_TIMEOUT;
+ wdt->wdtdev.min_hw_heartbeat_ms = 0;
+ wdt->wdtdev.timeout = CRONOS_WDG_DEFAULT_TIMEOUT;
+ wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+ wdt->wdtdev.parent = dev;
+
+ watchdog_set_restart_priority(&wdt->wdtdev, 128);
+
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+ dev_set_drvdata(dev, &wdt->wdtdev);
+
+ timeout = sony_cronos_wdt_read_timeout(wdt);
+ if (timeout)
+ wdt->wdtdev.timeout = timeout;
+
+ /* Set timeout from DT value if available */
+ watchdog_init_timeout(&wdt->wdtdev, 0, dev->parent);
+
+ if (timeout) {
+ sony_cronos_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
+ }
+
+ return devm_watchdog_register_device(dev, &wdt->wdtdev);
+}
+
+static int __maybe_unused sony_cronos_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdd))
+ return sony_cronos_wdt_stop(wdd);
+
+ return 0;
+}
+
+static int __maybe_unused sony_cronos_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdd))
+ return sony_cronos_wdt_start(wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sony_cronos_wdt_pm_ops, sony_cronos_wdt_suspend, sony_cronos_wdt_resume);
+
+static struct platform_driver sony_cronos_wdt_driver = {
+ .probe = sony_cronos_wdt_probe,
+ .driver = {
+ .name = "sony-cronos-watchdog",
+ .pm = &sony_cronos_wdt_pm_ops,
+ .of_match_table = sony_cronos_compatible_id_table,
+ },
+};
+module_platform_driver(sony_cronos_wdt_driver);
+
+MODULE_AUTHOR("Raptor Engineering, LLC <tpearson@raptorengineering.com>");
+MODULE_DESCRIPTION("WDT device driver for Sony Cronos SMCs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sony-cronos-watchdog");
--
2.39.5
On 20/10/2025 20:15, Timothy Pearson wrote:
> The Sony Cronos Platform Controller is a multi-purpose platform controller with
> an integrated watchdog. Add the watchdog driver for the Cronos SMC.
Please wrap commit message according to Linux coding style / submission
process (neither too early nor over the limit):
https://elixir.bootlin.com/linux/v6.4-rc1/source/Documentation/process/submitting-patches.rst#L597
>
> Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
> ---
...
> +
> +static const struct of_device_id sony_cronos_compatible_id_table[] = {
> + {
> + .compatible = "sony,cronos-watchdog",
Undocumented compatible, drop entire table.
> + },
> + {},
> +};
> +
...
> +
> +static SIMPLE_DEV_PM_OPS(sony_cronos_wdt_pm_ops, sony_cronos_wdt_suspend, sony_cronos_wdt_resume);
> +
> +static struct platform_driver sony_cronos_wdt_driver = {
> + .probe = sony_cronos_wdt_probe,
> + .driver = {
> + .name = "sony-cronos-watchdog",
> + .pm = &sony_cronos_wdt_pm_ops,
> + .of_match_table = sony_cronos_compatible_id_table,
Either this or platform module alias, not both. Since you do not have
compatibles, then drop this and keep the alias.
> + },
> +};
> +module_platform_driver(sony_cronos_wdt_driver);
> +
> +MODULE_AUTHOR("Raptor Engineering, LLC <tpearson@raptorengineering.com>");
> +MODULE_DESCRIPTION("WDT device driver for Sony Cronos SMCs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:sony-cronos-watchdog");
Best regards,
Krzysztof
© 2016 - 2026 Red Hat, Inc.