diff mbox series

[04/16] soc: renesas: Add SYSC driver for Renesas RZ/G3S

Message ID 20240822152801.602318-5-claudiu.beznea.uj@bp.renesas.com (mailing list archive)
State New
Headers show
Series Add initial USB support for the Renesas RZ/G3S SoC | expand

Commit Message

claudiu beznea Aug. 22, 2024, 3:27 p.m. UTC
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>

The RZ/G3S SYS Controller has 2 registers (one for PCIE one for USB) that
need to be configured before/after powering off/on the PCI or USB
ares. The bits in these registers control signals to PCIE and USB that
need to be de-asserted/asserted after/before power on/off event. For this
add SYSC controller driver that registers a reset controller driver on
auxiliary bus which allows USB, PCIE drivers to control these signals.

Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---

Hi, Philipp, Ulf, Geert, all,

In this series the control of USB and PCIE signals was implemented
though a reset control driver. This approach was chosen as a result
of looking though the HW manual and trying to understand how these
signals behave. HW manual can be downloaded from [1] (download
manual hardware button -> confirm -> extract archive ->
Deliverables -> r01uh1014ej0110-rzg3s.pdf).

The description of the USB and PCIE control registers is as follows:

SYS_USB_PWRRDY Register (Signal is called PWRRDY), Chapter 6.3.83:

Controls PWRRDY terminal of USB:
0: PWRRDY
1: PWRRDY down
When turning off the USB region power, set this bit to 1.
When turning on the USB region power, set this bit to 0.

SYS_PCIE_RST_RSM_B (Signal is called RST_RSM_B), Chapter 6.3.84:

Controls RST_RSM_B terminal of PCIe
0: RST_RSM_B=0
1: RST_RSM_B=1
Set RST_RSM_B=1 after PCIe power is applied.
When the power in the PCIe region is turned off, set RST_RSM_B=0
before turning off the power supply.

From this description I understood that the control of the USB PWRRDY,
PCIE RST_RSM_B signals and the power control for the domains
the USB, PCI belongs are different things [A].

As of Figure 41.1 (Power Domain and Power Supply) and Table 41.1 (Power
Domain to which Power Supply Pins Belong) the USB and PCIE belongs to
PD_ISOVCC power domain controlled though PMIC [B].

The USB, PCI signals are also reference in HW manual in the low power
consumption chapter describing the transition to different power
modes. E.g., chapter 41.6.1 (ALL_ON to VBATT), Table 41.8 (Example
Transition Flow Outline from ALL_ON Mode to VBATT Mode) says at
steps 6 and 7:

6. USB PHY PWRRDY signal control (if using USB) SYS_USB_PWRRDY
7. PCIe RST_RSM_B signal control (if using PCIe) SYS_PCIE_RST_RSM_B

Meaning these signals need to be controlled before going to VBATT
power mode (where the power supply to USB is turned off) [C].

Due to [A], [B] and [C] I chosed to have the implementation of these
signals though a reset control driver.

Other option I explored was though power domains as follows:
1/ registering one domain for USB, one of PCIE
2/ passed the domain ID to USB though device tree
3/ attach from USB PHY control driver to the USB power domain
   with dev_pm_domain_attach_by_name()
4/ and controlling the SYSC registers with
   pm_runtime_resume_and_get(usb_sysc_domain).

Please let me know what do you think about this!

Thank you,
Claudiu Beznea


[1] https://www.renesas.com/us/en/products/microcontrollers-microprocessors/rz-mpus/rzg3s-general-purpose-microprocessors-single-core-arm-cortex-a55-11-ghz-cpu-and-dual-core-cortex-m33-250

 drivers/reset/Kconfig                        |   7 +
 drivers/reset/Makefile                       |   1 +
 drivers/reset/reset-rzg3s-sysc.c             | 140 +++++++++++++++++++
 drivers/soc/renesas/Makefile                 |   1 +
 drivers/soc/renesas/rzg3s-sysc.c             | 113 +++++++++++++++
 include/linux/soc/renesas/rzg3s-sysc-reset.h |  24 ++++
 6 files changed, 286 insertions(+)
 create mode 100644 drivers/reset/reset-rzg3s-sysc.c
 create mode 100644 drivers/soc/renesas/rzg3s-sysc.c
 create mode 100644 include/linux/soc/renesas/rzg3s-sysc-reset.h
diff mbox series

Patch

diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 67bce340a87e..fbdf860b2293 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -218,6 +218,13 @@  config RESET_RZG2L_USBPHY_CTRL
 	  Support for USBPHY Control found on RZ/G2L family. It mainly
 	  controls reset and power down of the USB/PHY.
 
+config RESET_RZG3S_SYSC
+	tristate "Renesas RZ/G3S SYSC reset driver"
+	depends on ARCH_R9A08G045 || COMPILE_TEST
+	help
+	  Support for SYSC reset found on RZ/G3S family. It mainly
+	  controls reset on USB and PCIE.
+
 config RESET_SCMI
 	tristate "Reset driver controlled via ARM SCMI interface"
 	depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 27b0bbdfcc04..ee5ca21acc44 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -30,6 +30,7 @@  obj-$(CONFIG_RESET_QCOM_AOSS) += reset-qcom-aoss.o
 obj-$(CONFIG_RESET_QCOM_PDC) += reset-qcom-pdc.o
 obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o
 obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o
+obj-$(CONFIG_RESET_RZG3S_SYSC) += reset-rzg3s-sysc.o
 obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
 obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
 obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o
diff --git a/drivers/reset/reset-rzg3s-sysc.c b/drivers/reset/reset-rzg3s-sysc.c
new file mode 100644
index 000000000000..56af03f1d8a2
--- /dev/null
+++ b/drivers/reset/reset-rzg3s-sysc.c
@@ -0,0 +1,140 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G3S SYSC reset driver
+ *
+ * Copyright (C) 2024 Renesas Electronics Corp.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/soc/renesas/rzg3s-sysc-reset.h>
+
+#include <dt-bindings/reset/renesas,r9a08g045-sysc.h>
+
+#define RZG3S_SYSC_USB_PWRRDY		0xd70
+#define RZG3S_SYSC_PCIE_RST_RSM_B	0xd74
+#define RZG3S_SYSC_RESET_MASK		0x1
+
+/**
+ * struct rzg3s_sysc_reset_info - SYSC reset information
+ * @offset: offset to configure the reset
+ * @assert_val: value to write to register on assert
+ * @deassert_val: value to write to register on de-assert
+ */
+struct rzg3s_sysc_reset_info {
+	u16 offset;
+	u8 assert_val;
+	u8 deassert_val;
+};
+
+/**
+ * struct rzg3s_sysc_reset - SYSC reset
+ * @info: SYSC reset information
+ * @radev: SYSC reset auxiliary device
+ * @rcdev: reset controller device
+ */
+struct rzg3s_sysc_reset {
+	const struct rzg3s_sysc_reset_info *info;
+	struct rzg3s_sysc_reset_adev *radev;
+	struct reset_controller_dev rcdev;
+};
+
+#define to_rzg3s_sysc_reset(r)	container_of(r, struct rzg3s_sysc_reset, rcdev)
+
+static int rzg3s_sysc_reset_set(struct reset_controller_dev *rcdev,
+				unsigned long id, bool assert)
+{
+	struct rzg3s_sysc_reset *reset = to_rzg3s_sysc_reset(rcdev);
+	struct rzg3s_sysc_reset_adev *radev = reset->radev;
+	struct rzg3s_sysc_reset_info info = reset->info[id];
+	unsigned long flags;
+	u32 tmp;
+
+	spin_lock_irqsave(radev->lock, flags);
+	tmp = readl(radev->base + info.offset);
+	tmp &= ~RZG3S_SYSC_RESET_MASK;
+	tmp |= assert ? info.assert_val : info.deassert_val;
+	writel(tmp, radev->base + info.offset);
+	spin_unlock_irqrestore(radev->lock, flags);
+
+	return 0;
+}
+
+static int rzg3s_sysc_reset_assert(struct reset_controller_dev *rcdev,
+				   unsigned long id)
+{
+	return rzg3s_sysc_reset_set(rcdev, id, true);
+}
+
+static int rzg3s_sysc_reset_deassert(struct reset_controller_dev *rcdev,
+				     unsigned long id)
+{
+	return rzg3s_sysc_reset_set(rcdev, id, false);
+}
+
+static int rzg3s_sysc_reset_status(struct reset_controller_dev *rcdev,
+				   unsigned long id)
+{
+	struct rzg3s_sysc_reset *reset = to_rzg3s_sysc_reset(rcdev);
+	const struct rzg3s_sysc_reset_info info = reset->info[id];
+	struct rzg3s_sysc_reset_adev *radev = reset->radev;
+	u32 tmp;
+
+	tmp = readl(radev->base + info.offset);
+	tmp = !!(tmp & RZG3S_SYSC_RESET_MASK);
+
+	return info.assert_val ? tmp : !tmp;
+}
+
+static const struct reset_control_ops rzg3s_sysc_reset_ops = {
+	.assert = rzg3s_sysc_reset_assert,
+	.deassert = rzg3s_sysc_reset_deassert,
+	.status = rzg3s_sysc_reset_status,
+};
+
+static const struct rzg3s_sysc_reset_info rzg3s_sysc_reset_info[] = {
+	[R9A08G045_SYSC_RESET_USB] = {
+		.offset = RZG3S_SYSC_USB_PWRRDY, .assert_val = 1, .deassert_val = 0
+	},
+	[R9A08G045_SYSC_RESET_PCIE] = {
+		.offset = RZG3S_SYSC_PCIE_RST_RSM_B, .assert_val = 0, .deassert_val = 1
+	},
+};
+
+static int rzg3s_sysc_reset_probe(struct auxiliary_device *adev,
+				  const struct auxiliary_device_id *id)
+{
+	struct rzg3s_sysc_reset_adev *reset_adev = to_rzg3s_sysc_reset_adev(adev);
+	struct device *dev = &adev->dev;
+	struct rzg3s_sysc_reset *reset;
+
+	reset = devm_kzalloc(dev, sizeof(*reset), GFP_KERNEL);
+	if (!reset)
+		return -ENOMEM;
+
+	reset->radev = reset_adev;
+	reset->info = rzg3s_sysc_reset_info;
+
+	reset->rcdev.ops = &rzg3s_sysc_reset_ops;
+	reset->rcdev.of_reset_n_cells = 1;
+	reset->rcdev.nr_resets = ARRAY_SIZE(rzg3s_sysc_reset_info);
+	reset->rcdev.of_node = dev->parent->of_node;
+	reset->rcdev.dev = dev;
+
+	return devm_reset_controller_register(dev, &reset->rcdev);
+}
+
+static const struct auxiliary_device_id rzg3s_sysc_reset_ids[] = {
+	{ .name = "rzg3s_sysc.reset" },
+	{ }
+};
+MODULE_DEVICE_TABLE(auxiliary, rzg3s_sysc_reset_ids);
+
+static struct auxiliary_driver rzg3s_sysc_reset_driver = {
+	.probe		= rzg3s_sysc_reset_probe,
+	.id_table	= rzg3s_sysc_reset_ids,
+};
+module_auxiliary_driver(rzg3s_sysc_reset_driver);
diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile
index 734f8f8cefa4..74c72ac46f91 100644
--- a/drivers/soc/renesas/Makefile
+++ b/drivers/soc/renesas/Makefile
@@ -6,6 +6,7 @@  obj-$(CONFIG_SOC_RENESAS)	+= renesas-soc.o
 ifdef CONFIG_SMP
 obj-$(CONFIG_ARCH_R9A06G032)	+= r9a06g032-smp.o
 endif
+obj-$(CONFIG_ARCH_R9A08G045)	+= rzg3s-sysc.o
 
 # Family
 obj-$(CONFIG_PWC_RZV2M)		+= pwc-rzv2m.o
diff --git a/drivers/soc/renesas/rzg3s-sysc.c b/drivers/soc/renesas/rzg3s-sysc.c
new file mode 100644
index 000000000000..e664d29ce5c3
--- /dev/null
+++ b/drivers/soc/renesas/rzg3s-sysc.c
@@ -0,0 +1,113 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RZ/G3S System controller driver
+ *
+ * Copyright (C) 2024 Renesas Electronics Corp.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/platform_device.h>
+
+#include <linux/soc/renesas/rzg3s-sysc-reset.h>
+
+/**
+ * struct rzg3s_sysc - SYSC private data structure
+ * @base: base address
+ * @dev: device
+ * @lock: lock
+ */
+struct rzg3s_sysc {
+	void __iomem *base;
+	struct device *dev;
+	spinlock_t lock;
+};
+
+static void rzg3s_sysc_reset_adev_release(struct device *dev)
+{
+	struct auxiliary_device *adev = to_auxiliary_dev(dev);
+	struct rzg3s_sysc_reset_adev *reset_adev = to_rzg3s_sysc_reset_adev(adev);
+
+	kfree(reset_adev);
+}
+
+static void rzg3s_sysc_reset_unregister_adev(void *adev)
+{
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+static int rzg3s_sysc_reset_probe(struct rzg3s_sysc *sysc, const char *adev_name,
+				  u32 adev_id)
+{
+	struct rzg3s_sysc_reset_adev *radev;
+	struct auxiliary_device *adev;
+	int ret;
+
+	radev = kzalloc(sizeof(*radev), GFP_KERNEL);
+	if (!radev)
+		return -ENOMEM;
+
+	radev->base = sysc->base;
+	radev->lock = &sysc->lock;
+
+	adev = &radev->adev;
+	adev->name = adev_name;
+	adev->dev.parent = sysc->dev;
+	adev->dev.release = rzg3s_sysc_reset_adev_release;
+	adev->id = adev_id;
+
+	ret = auxiliary_device_init(adev);
+	if (ret)
+		return ret;
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(sysc->dev, rzg3s_sysc_reset_unregister_adev, adev);
+}
+
+static int rzg3s_sysc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rzg3s_sysc *sysc;
+
+	sysc = devm_kzalloc(dev, sizeof(*sysc), GFP_KERNEL);
+	if (!sysc)
+		return -ENOMEM;
+
+	sysc->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(sysc->base))
+		return PTR_ERR(sysc->base);
+
+	sysc->dev = dev;
+	spin_lock_init(&sysc->lock);
+
+	return rzg3s_sysc_reset_probe(sysc, "reset", 0);
+}
+
+static const struct of_device_id rzg3s_sysc_match[] = {
+	{ .compatible = "renesas,r9a08g045-sysc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rzg3s_sysc_match);
+
+static struct platform_driver rzg3s_sysc_driver = {
+	.driver = {
+		.name = "renesas-rzg3s-sysc",
+		.of_match_table = rzg3s_sysc_match
+	},
+	.probe = rzg3s_sysc_probe
+};
+
+static int __init rzg3s_sysc_init(void)
+{
+	return platform_driver_register(&rzg3s_sysc_driver);
+}
+subsys_initcall(rzg3s_sysc_init);
+
+MODULE_DESCRIPTION("Renesas RZ/G3S System Controller Driver");
+MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/soc/renesas/rzg3s-sysc-reset.h b/include/linux/soc/renesas/rzg3s-sysc-reset.h
new file mode 100644
index 000000000000..813cbe82a68a
--- /dev/null
+++ b/include/linux/soc/renesas/rzg3s-sysc-reset.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __SOC_RENESAS_SYSC_RESET_RZG3S_H
+#define __SOC_RENESAS_SYSC_RESET_RZG3S_H
+
+#include <linux/auxiliary_bus.h>
+#include <linux/spinlock_types.h>
+#include <linux/container_of.h>
+
+/**
+ * struct rzg3s_sysc_reset_adev - SYSC reset auxiliary device
+ * @base: base address
+ * @lock: lock
+ * @adev: auxiliary device
+ */
+struct rzg3s_sysc_reset_adev {
+	void __iomem *base;
+	spinlock_t *lock;
+	struct auxiliary_device adev;
+};
+
+#define to_rzg3s_sysc_reset_adev(a)	container_of(a, struct rzg3s_sysc_reset_adev, adev)
+
+#endif