soc:starfive: add jh7110 pmu driver.
authorsamin <samin.guo@starfivetech.com>
Fri, 14 Jan 2022 07:10:00 +0000 (15:10 +0800)
committersamin <samin.guo@starfivetech.com>
Fri, 14 Jan 2022 07:44:44 +0000 (15:44 +0800)
The JH7110 PMU can dynamically switch on or off power domians and set
the power-on and power-off sequence.

API Instructions refer to include/soc/starfive/jh7110_pmu.h

Signed-off-by: samin <samin.guo@starfivetech.com>
drivers/soc/starfive/Kconfig [new file with mode: 0644]
drivers/soc/starfive/Makefile [new file with mode: 0644]
drivers/soc/starfive/jh7110_pmu.c [new file with mode: 0644]
include/soc/starfive/jh7110_pmu.h [new file with mode: 0644]

diff --git a/drivers/soc/starfive/Kconfig b/drivers/soc/starfive/Kconfig
new file mode 100644 (file)
index 0000000..d246df4
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config STARFIVE_PMU
+       bool "Support PMU for StarFive Soc"
+       depends on SOC_STARFIVE || COMPILE_TEST
+       default SOC_STARFIVE_JH7110
+       help
+         Support PMU for StarFive Soc.
diff --git a/drivers/soc/starfive/Makefile b/drivers/soc/starfive/Makefile
new file mode 100644 (file)
index 0000000..296eb75
--- /dev/null
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_STARFIVE_PMU)     += jh7110_pmu.o
diff --git a/drivers/soc/starfive/jh7110_pmu.c b/drivers/soc/starfive/jh7110_pmu.c
new file mode 100644 (file)
index 0000000..5e50220
--- /dev/null
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PMU driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 samin <samin.guo@starfivetech.com>
+ */
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <soc/starfive/jh7110_pmu.h>
+
+/* register define */
+#define HW_EVENT_TURN_ON_MASK          0x04
+#define HW_EVENT_TURN_OFF_MASK         0x08
+#define SW_TURN_ON_POWER_MODE          0x0C
+#define SW_TURN_OFF_POWER_MODE         0x10
+#define THE_SHOLD_SEQ_TIMEOUT          0x14
+#define POWER_DOMAIN_CASCADE_0         0x18
+#define POWER_DOMAIN_CASCADE_1         0x1C
+#define POWER_DOMAIN_CASCADE_2         0x20
+#define POWER_DOMAIN_CASCADE_3         0x24
+#define POWER_DOMAIN_CASCADE_4         0x28
+#define POWER_DOMAIN_CASCADE_5         0x2C
+#define POWER_DOMAIN_CASCADE_6         0x30
+#define POWER_DOMAIN_CASCADE_7         0x34
+#define POWER_DOMAIN_CASCADE_8         0x38
+#define POWER_DOMAIN_CASCADE_9         0x3C
+#define POWER_DOMAIN_CASCADE_10                0x40
+#define SW_ENCOURAGE                   0x44
+#define PMU_INT_MASK                   0x48
+#define PCH_BYPASS                     0x4C
+#define PCH_PSTATE                     0x50
+#define PCH_TIMEOUT                    0x54
+#define LP_TIMEOUT                     0x58
+#define HW_TURN_ON_MODE                        0x5C
+#define CURR_POWER_MODE                        0x80
+#define CURR_SEQ_STATE                 0x84
+#define PMU_EVENT_STATUS               0x88
+#define PMU_INT_STATUS                 0x8C
+#define HW_EVENT_RECORD                        0x90
+#define HW_EVENT_TYPE_RECORD           0x94
+#define PCH_PACTIVE_STATUS             0x98
+
+/* pmu int status */
+#define INT_SEQ_DONE_EVENT             BIT(0)
+#define INT_HW_REQ_EVENT               BIT(1)
+#define INT_SW_FAIL_EVENT              GENMASK(3, 2)
+#define INT_HW_FAIL_EVENT              GENMASK(5, 4)
+#define INT_PCH_FAIL_EVENT             GENMASK(8, 6)
+#define PMU_INT_EVENT_ALL              GENMASK(8, 0)
+
+/* sw encourage cfg */
+#define SW_MODE_ENCOURAGE_EN_LO                0x05
+#define SW_MODE_ENCOURAGE_EN_HI                0x50
+#define SW_MODE_ENCOURAGE_DIS_LO       0x0A
+#define SW_MODE_ENCOURAGE_DIS_HI       0xA0
+#define SW_MODE_ENCOURAGE_ON           0xFF
+
+u32 power_domain_cascade[] = {
+       GENMASK(4, 0),
+       GENMASK(9, 5),
+       GENMASK(14, 10),
+       GENMASK(19, 15),
+       GENMASK(24, 20),
+       GENMASK(29, 25)
+};
+
+static void __iomem *pmu_base;
+
+struct starfive_pmu {
+       struct device   *dev;
+       spinlock_t lock;
+       int irq;
+};
+
+static inline u32 pmu_readl(u32 offset)
+{
+       return readl(pmu_base + offset);
+}
+
+static inline void pmu_writel(u32 val, u32 offset)
+{
+       writel(val, pmu_base + offset);
+}
+
+static bool pmu_get_current_power_mode(u32 domain)
+{
+       return pmu_readl(CURR_POWER_MODE) & domain;
+}
+
+static void starfive_pmu_int_enable(u32 mask, bool enable)
+{
+       u32 val = pmu_readl(PMU_INT_MASK);
+
+       if (enable)
+               val |= mask;
+       else
+               val &= ~mask;
+
+       pmu_writel(val, PMU_INT_MASK);
+}
+/*
+ * mask the hw_evnet
+ */
+static void starfive_pmu_hw_event_turn_on_mask(u32 hw_event, bool mask)
+{
+       u32 val = pmu_readl(HW_EVENT_TURN_ON_MASK);
+
+       if (mask)
+               val |= hw_event;
+       else
+               val &= ~hw_event;
+
+       pmu_writel(val, HW_EVENT_TURN_ON_MASK);
+}
+
+void starfive_power_domain_set(u32 domain, bool enable)
+{
+       u32 val, mode;
+       u32 encourage_lo, encourage_hi;
+
+       if (!(pmu_get_current_power_mode(domain) ^ enable)) {
+               pr_info("[pmu]domain is already  %#x %sable status.\n",
+                                       domain, enable ? "en" : "dis");
+               return;
+       }
+
+       if (enable) {
+               mode = SW_TURN_ON_POWER_MODE;
+               encourage_lo = SW_MODE_ENCOURAGE_EN_LO;
+               encourage_hi = SW_MODE_ENCOURAGE_EN_HI;
+       } else {
+               mode = SW_TURN_OFF_POWER_MODE;
+               encourage_lo = SW_MODE_ENCOURAGE_DIS_LO;
+               encourage_hi = SW_MODE_ENCOURAGE_DIS_HI;
+       }
+
+       pr_info("[pmu]domain: %#x %sable\n", domain, enable ? "en" : "dis");
+       val = pmu_readl(mode);
+       val |= domain;
+       pmu_writel(val, mode);
+
+       /* write SW_ENCOURAGE to make the configuration take effect */
+       pmu_writel(SW_MODE_ENCOURAGE_ON, SW_ENCOURAGE);
+       pmu_writel(encourage_lo, SW_ENCOURAGE);
+       pmu_writel(encourage_hi, SW_ENCOURAGE);
+}
+EXPORT_SYMBOL(starfive_power_domain_set);
+
+void starfive_power_domain_set_by_hwevent(u32 domain, u32 event, bool enable)
+{
+       u32 val;
+
+       val = pmu_readl(HW_TURN_ON_MODE);
+
+       if (enable)
+               val |= domain;
+       else
+               val &= ~domain;
+
+       pmu_writel(val, HW_TURN_ON_MODE);
+
+       starfive_pmu_hw_event_turn_on_mask(event, enable);
+}
+EXPORT_SYMBOL(starfive_power_domain_set_by_hwevent);
+
+static irqreturn_t starfive_pmu_interrupt(int irq, void *data)
+{
+       struct starfive_pmu *pmu = data;
+       unsigned long flags;
+       u32 val;
+
+       spin_lock_irqsave(&pmu->lock, flags);
+       /* disable interrupts */
+       starfive_pmu_hw_event_turn_on_mask(PMU_INT_EVENT_ALL, false);
+       val = pmu_readl(PMU_INT_STATUS);
+
+       if (val & INT_SEQ_DONE_EVENT)
+               dev_info(pmu->dev, "sequence done.\n");
+       if (val & INT_HW_REQ_EVENT)
+               dev_info(pmu->dev, "hardware encourage requestion.\n");
+       if (val & INT_SW_FAIL_EVENT)
+               dev_err(pmu->dev, "software encourage fail.\n");
+       if (val & INT_HW_FAIL_EVENT)
+               dev_err(pmu->dev, "hardware encourage fail.\n");
+       if (val & INT_PCH_FAIL_EVENT)
+               dev_err(pmu->dev, "p-channel fail event.\n");
+
+       /* clear interrupts */
+       pmu_writel(val, PMU_INT_STATUS);
+       pmu_writel(val, PMU_EVENT_STATUS);
+
+       starfive_pmu_hw_event_turn_on_mask(PMU_INT_EVENT_ALL, true);
+       spin_unlock_irqrestore(&pmu->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+static int starfive_pmu_pad_order_get(u32 domain, bool on_off)
+{
+       unsigned int group;
+       u32 val, offset;
+
+       group = domain / 3;
+       offset = (domain % 3) << 1;
+
+       val = pmu_readl(POWER_DOMAIN_CASCADE_0 + group * 4);
+       if (on_off)
+               val &= power_domain_cascade[offset + 1];
+       else
+               val &= power_domain_cascade[offset];
+
+       return val;
+}
+
+static void starfive_pmu_pad_order_set(u32 domain, bool on_off, u32 order)
+{
+       unsigned int group;
+       u32 val, offset;
+
+       group = domain / 3;
+       offset = (domain % 3) << 1;
+
+       val = pmu_readl(POWER_DOMAIN_CASCADE_0 + group * 4);
+       if (on_off)
+               val |= (order << __ffs(power_domain_cascade[offset + 1]));
+       else
+               val |= (order << __ffs(power_domain_cascade[offset]));
+
+       pmu_writel(val, POWER_DOMAIN_CASCADE_0 + group * 4);
+}
+
+int starfive_power_domain_order_on_get(u32 domain)
+{
+       return starfive_pmu_pad_order_get(domain, true);
+}
+EXPORT_SYMBOL(starfive_power_domain_order_on_get);
+
+int starfive_power_domain_order_off_get(u32 domain)
+{
+       return starfive_pmu_pad_order_get(domain, false);
+}
+EXPORT_SYMBOL(starfive_power_domain_order_off_get);
+
+void starfive_power_domain_order_on_set(u32 domain, u32 order)
+{
+       starfive_pmu_pad_order_set(domain, true, order);
+}
+EXPORT_SYMBOL(starfive_power_domain_order_on_set);
+
+void starfive_power_domain_order_off_set(u32 domain, u32 order)
+{
+       starfive_pmu_pad_order_set(domain, false, order);
+}
+EXPORT_SYMBOL(starfive_power_domain_order_off_set);
+
+static int starfive_pmu_probe(struct platform_device *pdev)
+{
+       struct starfive_pmu *pmu;
+       struct device *dev = &pdev->dev;
+       int ret;
+
+       pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
+       if (!pmu)
+               return -ENOMEM;
+
+       pmu->dev = dev;
+       dev->driver_data = pmu;
+
+       pmu_base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(pmu_base))
+               return PTR_ERR(pmu_base);
+
+       pmu->irq = platform_get_irq(pdev, 0);
+       if (pmu->irq < 0)
+               return pmu->irq;
+
+       ret = devm_request_irq(dev, pmu->irq, starfive_pmu_interrupt,
+                              0, pdev->name, pmu);
+       if (ret)
+               dev_err(dev, "request irq failed.\n");
+
+       spin_lock_init(&pmu->lock);
+       starfive_pmu_int_enable(PMU_INT_EVENT_ALL, true);
+
+       return ret;
+}
+
+static int starfive_pmu_remove(struct platform_device *dev)
+{
+       starfive_pmu_int_enable(PMU_INT_EVENT_ALL, false);
+
+       return 0;
+}
+
+static const struct of_device_id starfive_pmu_dt_ids[] = {
+       { .compatible = "starfive,jh7110-pmu" },
+       { /* sentinel */ }
+};
+
+static struct platform_driver starfive_pmu_driver = {
+       .probe          = starfive_pmu_probe,
+       .remove         = starfive_pmu_remove,
+       .driver         = {
+               .name   = "starfive-pmu",
+               .of_match_table = starfive_pmu_dt_ids,
+       },
+};
+
+static int __init starfive_pmu_init(void)
+{
+       return platform_driver_register(&starfive_pmu_driver);
+}
+
+static void __exit starfive_pmu_exit(void)
+{
+       platform_driver_unregister(&starfive_pmu_driver);
+}
+subsys_initcall(starfive_pmu_init);
+module_exit(starfive_pmu_exit);
+
+MODULE_AUTHOR("samin <samin.guo@starfivetech.com>");
+MODULE_DESCRIPTION("StarFive JH7110 PMU Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/soc/starfive/jh7110_pmu.h b/include/soc/starfive/jh7110_pmu.h
new file mode 100644 (file)
index 0000000..d549696
--- /dev/null
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Reset driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 samin <samin.guo@starfivetech.com>
+ */
+
+#ifndef __SOC_STARFIVE_JH7110_PMU_H__
+#define __SOC_STARFIVE_JH7110_PMU_H__
+
+#include <linux/types.h>
+
+/* SW/HW Power domain id  */
+#define POWER_DOMAIN_SYSTOP            (1 << 0)
+#define POWER_DOMAIN_CPU               (1 << 1)
+#define POWER_DOMAIN_GPUA              (1 << 2)
+#define POWER_DOMAIN_VDEC              (1 << 3)
+#define POWER_DOMAIN_JPU               POWER_DOMAIN_VDEC
+#define POWER_DOMAIN_VOUT              (1 << 4)
+#define POWER_DOMAIN_ISP               (1 << 5)
+#define POWER_DOMAIN_VENC              (1 << 6)
+#define POWER_DOMAIN_GPUB              (1 << 7)
+
+enum PMU_HARD_EVENT {
+       RTC_EVENT = 0,
+       GMAC_EVENT,
+       RFU,
+       RGPIO0_EVENT,
+       RGPIO1_EVENT,
+       RGPIO2_EVENT,
+       RGPIO3_EVENT,
+       GPU_EVENT,
+};
+
+/*
+ * @func: starfive_power_domain_set
+ * @dec: power domain turn-on/off by software
+ * @domain: power domain id
+ *     POWER_DOMAIN_SYSTOP:
+ *     POWER_DOMAIN_CPU
+ *     POWER_DOMAIN_GPUA
+ *     POWER_DOMAIN_VDEC
+ *     POWER_DOMAIN_VOUT
+ *     POWER_DOMAIN_ISP
+ *     POWER_DOMAIN_VENC
+ *     POWER_DOMAIN_GPUB
+ * @enable: 1:enable 0:disable
+ */
+void starfive_power_domain_set(u32 domain, bool enable);
+
+/*
+ * @func: starfive_pmu_hw_encourage
+ * @dec: power domain turn-on/off by HW envent(interrupt)
+ * @domain: power domain id
+ * @event: Hardware trigger event. PMU_HARD_EVENT:
+       RTC_EVENT = 0,
+       GMAC_EVENT,
+       RFU,
+       RGPIO0_EVENT,
+       RGPIO1_EVENT,
+       RGPIO2_EVENT,
+       RGPIO3_EVENT,
+       GPU_EVENT,
+ * @enable: 1:enable 0:disable
+ *
+ * @for example:
+ *     starfive_power_domain_set_by_hwevent(POWER_DOMAIN_VDEC, RTC_EVENT, 0);
+ *
+ *     Means that when the RTC alarm is interrupted, the hardware
+ *     is triggered to close the power domain of VDEC.
+ */
+void starfive_power_domain_set_by_hwevent(u32 domain, u32 event, bool enable);
+
+/*
+ * @func: starfive_power_domain_order_on_get
+ * @dec: PMU power domian power on order get.
+ * @domian: powerff domain id
+ */
+int starfive_power_domain_order_on_get(u32 domain);
+
+/*
+ * @func: starfive_power_domain_order_off_get
+ * @dec: PMU power domian power off order get.
+ * @domian: power domain id
+ */
+int starfive_power_domain_order_off_get(u32 domain);
+
+/*
+ * @func: starfive_power_domain_order_on_set
+ * @dec: PMU power domian power on order set.
+ * @domian: powerff domain id
+ * @order: the poweron order of domain
+ */
+void starfive_power_domain_order_on_set(u32 domain, u32 order);
+
+/*
+ * @func: starfive_power_domain_order_off_set
+ * @dec: PMU power domian power off order set.
+ * @domian: power domain id
+ * @order: the poweroff order of domain
+ */
+void starfive_power_domain_order_off_set(u32 domain, u32 order);
+
+#endif /* __SOC_STARFIVE_JH7110_PMU_H__ */