aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig21
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/bcm2835-pm.c28
-rw-r--r--drivers/mfd/rp1.c376
-rw-r--r--drivers/mfd/simple-mfd-i2c.c11
5 files changed, 428 insertions, 9 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6cec1858947b..3a5166cd1883 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1292,6 +1292,16 @@ config MFD_SY7636A
To enable support for building sub-devices as modules,
choose M here.
+config MFD_RASPBERRYPI_POE_HAT
+ tristate "Raspberry Pi PoE HAT MFD"
+ depends on I2C
+ select MFD_SIMPLE_MFD_I2C
+ help
+ This module supports the PWM fan controller found on the Raspberry Pi
+ POE and POE+ HAT boards, and the power supply driver on the POE+ HAT.
+ (Functionally it relies on MFD_SIMPLE_MFD_I2C to provide the framework
+ that loads the child drivers).
+
config MFD_RDC321X
tristate "RDC R-321x southbridge"
select MFD_CORE
@@ -2493,6 +2503,17 @@ config MFD_LS2K_BMC_CORE
The display is enabled by default in the driver, while the IPMI interface
is enabled independently through the IPMI_LS2K option in the IPMI section.
+config MFD_RP1
+ tristate "RP1 MFD driver"
+ depends on PCI
+ select MFD_CORE
+ help
+ Support for the RP1 peripheral chip.
+
+ This driver provides support for the Raspberry Pi RP1 peripheral chip.
+ It is responsible for enabling the Device Tree node once the PCIe endpoint
+ has been configured, and handling interrupts.
+
config MFD_QNAP_MCU
tristate "QNAP microcontroller unit core driver"
depends on SERIAL_DEV_BUS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 865e9f12faff..711255493002 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -300,6 +300,7 @@ 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_RP1) += rp1.o
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o
diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c
index 3cb2b9423121..8b31775da7b6 100644
--- a/drivers/mfd/bcm2835-pm.c
+++ b/drivers/mfd/bcm2835-pm.c
@@ -69,12 +69,30 @@ static int bcm2835_pm_get_pdata(struct platform_device *pdev,
return 0;
}
+static const struct of_device_id bcm2835_pm_of_match[] = {
+ { .compatible = "brcm,bcm2835-pm-wdt", },
+ { .compatible = "brcm,bcm2835-pm", },
+ { .compatible = "brcm,bcm2711-pm", },
+ { .compatible = "brcm,bcm2712-pm", .data = (const void *)1},
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
+
static int bcm2835_pm_probe(struct platform_device *pdev)
{
+ const struct of_device_id *of_id;
struct device *dev = &pdev->dev;
struct bcm2835_pm *pm;
+ bool is_2712;
int ret;
+ of_id = of_match_node(bcm2835_pm_of_match, pdev->dev.of_node);
+ if (!of_id) {
+ dev_err(&pdev->dev, "Failed to match compatible string\n");
+ return -EINVAL;
+ }
+ is_2712 = !!of_id->data;
+
pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
if (!pm)
return -ENOMEM;
@@ -97,21 +115,13 @@ static int bcm2835_pm_probe(struct platform_device *pdev)
* bcm2835-pm binding as the key for whether we can reference
* the full PM register range and support power domains.
*/
- if (pm->asb)
+ if (pm->asb || is_2712)
return devm_mfd_add_devices(dev, -1, bcm2835_power_devs,
ARRAY_SIZE(bcm2835_power_devs),
NULL, 0, NULL);
return 0;
}
-static const struct of_device_id bcm2835_pm_of_match[] = {
- { .compatible = "brcm,bcm2835-pm-wdt", },
- { .compatible = "brcm,bcm2835-pm", },
- { .compatible = "brcm,bcm2711-pm", },
- {},
-};
-MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
-
static struct platform_driver bcm2835_pm_driver = {
.probe = bcm2835_pm_probe,
.driver = {
diff --git a/drivers/mfd/rp1.c b/drivers/mfd/rp1.c
new file mode 100644
index 000000000000..834125d474b2
--- /dev/null
+++ b/drivers/mfd/rp1.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-22 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/rp1_platform.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/mfd/rp1.h>
+
+/* TO DO:
+ * 1. Occasional shutdown crash - RP1 being closed before its children?
+ * 2. DT mode interrupt handling.
+ */
+
+#define RP1_DRIVER_NAME "rp1"
+
+#define PCI_VENDOR_ID_RPI 0x1de4
+#define PCI_DEVICE_ID_RP1_C0 0x0001
+#define PCI_DEVICE_REV_RP1_C0 2
+
+#define RP1_ACTUAL_IRQS RP1_INT_END
+#define RP1_IRQS RP1_ACTUAL_IRQS
+
+#define RP1_SYSCLK_RATE 200000000
+#define RP1_SYSCLK_FPGA_RATE 60000000
+
+// Don't want to include the whole sysinfo reg header
+#define SYSINFO_CHIP_ID_OFFSET 0x00000000
+#define SYSINFO_PLATFORM_OFFSET 0x00000004
+
+#define REG_RW 0x000
+#define REG_SET 0x800
+#define REG_CLR 0xc00
+
+// MSIX CFG registers start at 0x8
+#define MSIX_CFG(x) (0x8 + (4 * (x)))
+
+#define MSIX_CFG_IACK_EN BIT(3)
+#define MSIX_CFG_IACK BIT(2)
+#define MSIX_CFG_TEST BIT(1)
+#define MSIX_CFG_ENABLE BIT(0)
+
+#define INTSTATL 0x108
+#define INTSTATH 0x10c
+
+struct rp1_dev {
+ struct pci_dev *pdev;
+ struct device *dev;
+ resource_size_t bar_start;
+ resource_size_t bar_end;
+ struct clk *sys_clk;
+ struct irq_domain *domain;
+ struct irq_data *pcie_irqds[64];
+ void __iomem *msix_cfg_regs;
+};
+
+static bool rp1_level_triggered_irq[RP1_ACTUAL_IRQS] = { 0 };
+
+static struct rp1_dev *g_rp1;
+static u32 g_chip_id, g_platform;
+
+static void dump_bar(struct pci_dev *pdev, unsigned int bar)
+{
+ dev_info(&pdev->dev,
+ "bar%d len 0x%llx, start 0x%llx, end 0x%llx, flags, 0x%lx\n",
+ bar,
+ pci_resource_len(pdev, bar),
+ pci_resource_start(pdev, bar),
+ pci_resource_end(pdev, bar),
+ pci_resource_flags(pdev, bar));
+}
+
+static void msix_cfg_set(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+ writel(value, rp1->msix_cfg_regs + REG_SET + MSIX_CFG(hwirq));
+}
+
+static void msix_cfg_clr(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+ writel(value, rp1->msix_cfg_regs + REG_CLR + MSIX_CFG(hwirq));
+}
+
+static void rp1_mask_irq(struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+ pci_msi_mask_irq(pcie_irqd);
+}
+
+static void rp1_unmask_irq(struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+ pci_msi_unmask_irq(pcie_irqd);
+}
+
+static int rp1_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ unsigned int hwirq = (unsigned int)irqd->hwirq;
+ int ret = 0;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ dev_dbg(rp1->dev, "MSIX IACK EN for irq %d\n", hwirq);
+ msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK_EN);
+ rp1_level_triggered_irq[hwirq] = true;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ msix_cfg_clr(rp1, hwirq, MSIX_CFG_IACK_EN);
+ rp1_level_triggered_irq[hwirq] = false;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int rp1_irq_set_affinity(struct irq_data *irqd, const struct cpumask *dest, bool force)
+{
+ struct rp1_dev *rp1 = irqd->domain->host_data;
+ struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+ return msi_domain_set_affinity(pcie_irqd, dest, force);
+}
+
+static struct irq_chip rp1_irq_chip = {
+ .name = "rp1_irq_chip",
+ .irq_mask = rp1_mask_irq,
+ .irq_unmask = rp1_unmask_irq,
+ .irq_set_type = rp1_irq_set_type,
+ .irq_set_affinity = rp1_irq_set_affinity,
+};
+
+static void rp1_chained_handle_irq(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct rp1_dev *rp1 = desc->irq_data.chip_data;
+ unsigned int hwirq = desc->irq_data.hwirq & 0x3f;
+ int new_irq;
+
+ rp1 = g_rp1;
+
+ chained_irq_enter(chip, desc);
+
+ new_irq = irq_find_mapping(rp1->domain, hwirq);
+ generic_handle_irq(new_irq);
+ if (rp1_level_triggered_irq[hwirq])
+ msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK);
+
+ chained_irq_exit(chip, desc);
+}
+
+static int rp1_irq_xlate(struct irq_domain *d, struct device_node *node,
+ const u32 *intspec, unsigned int intsize,
+ unsigned long *out_hwirq, unsigned int *out_type)
+{
+ struct rp1_dev *rp1 = d->host_data;
+ struct irq_data *pcie_irqd;
+ unsigned long hwirq;
+ int pcie_irq;
+ int ret;
+
+ ret = irq_domain_xlate_twocell(d, node, intspec, intsize,
+ &hwirq, out_type);
+ if (!ret) {
+ pcie_irq = pci_irq_vector(rp1->pdev, hwirq);
+ pcie_irqd = irq_get_irq_data(pcie_irq);
+ rp1->pcie_irqds[hwirq] = pcie_irqd;
+ *out_hwirq = hwirq;
+ }
+ return ret;
+}
+
+static int rp1_irq_activate(struct irq_domain *d, struct irq_data *irqd,
+ bool reserve)
+{
+ struct rp1_dev *rp1 = d->host_data;
+ struct irq_data *pcie_irqd;
+
+ pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+ msix_cfg_set(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+ return irq_domain_activate_irq(pcie_irqd, reserve);
+}
+
+static void rp1_irq_deactivate(struct irq_domain *d, struct irq_data *irqd)
+{
+ struct rp1_dev *rp1 = d->host_data;
+ struct irq_data *pcie_irqd;
+
+ pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+ msix_cfg_clr(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+ return irq_domain_deactivate_irq(pcie_irqd);
+}
+
+static const struct irq_domain_ops rp1_domain_ops = {
+ .xlate = rp1_irq_xlate,
+ .activate = rp1_irq_activate,
+ .deactivate = rp1_irq_deactivate,
+};
+
+static inline dma_addr_t rp1_io_to_phys(struct rp1_dev *rp1, unsigned int offset)
+{
+ return rp1->bar_start + offset;
+}
+
+static u32 rp1_reg_read(struct rp1_dev *rp1, unsigned int base_addr, u32 offset)
+{
+ dma_addr_t phys = rp1_io_to_phys(rp1, base_addr);
+ void __iomem *regblock = ioremap(phys, 0x1000);
+ u32 value = readl(regblock + offset);
+
+ iounmap(regblock);
+ return value;
+}
+
+void rp1_get_platform(u32 *chip_id, u32 *platform)
+{
+ if (chip_id)
+ *chip_id = g_chip_id;
+ if (platform)
+ *platform = g_platform;
+}
+EXPORT_SYMBOL_GPL(rp1_get_platform);
+
+static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct reset_control *reset;
+ struct platform_device *pcie_pdev;
+ struct device_node *rp1_node;
+ struct rp1_dev *rp1;
+ int err = 0;
+ int i;
+
+ reset = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+ reset_control_reset(reset);
+
+ dump_bar(pdev, 0);
+ dump_bar(pdev, 1);
+
+ if (pci_resource_len(pdev, 1) <= 0x10000) {
+ dev_err(&pdev->dev,
+ "Not initialised - is the firmware running?\n");
+ return -EINVAL;
+ }
+
+ /* enable pci device */
+ err = pcim_enable_device(pdev);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Enabling PCI device has failed: %d",
+ err);
+ return err;
+ }
+
+ pci_set_master(pdev);
+
+ err = pci_alloc_irq_vectors(pdev, RP1_IRQS, RP1_IRQS,
+ PCI_IRQ_MSIX);
+ if (err != RP1_IRQS) {
+ dev_err(&pdev->dev, "pci_alloc_irq_vectors failed - %d\n", err);
+ return err;
+ }
+
+ rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL);
+ if (!rp1)
+ return -ENOMEM;
+
+ rp1->pdev = pdev;
+ rp1->dev = &pdev->dev;
+
+ pci_set_drvdata(pdev, rp1);
+
+ rp1->bar_start = pci_resource_start(pdev, 1);
+ rp1->bar_end = pci_resource_end(pdev, 1);
+
+ // Get chip id
+ g_chip_id = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_CHIP_ID_OFFSET);
+ g_platform = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_PLATFORM_OFFSET);
+ dev_info(&pdev->dev, "chip_id 0x%x%s\n", g_chip_id,
+ (g_platform & RP1_PLATFORM_FPGA) ? " FPGA" : "");
+ if (g_chip_id != RP1_C0_CHIP_ID) {
+ dev_err(&pdev->dev, "wrong chip id (%x)\n", g_chip_id);
+ return -EINVAL;
+ }
+
+ rp1_node = of_find_node_by_name(NULL, "rp1");
+ if (!rp1_node) {
+ dev_err(&pdev->dev, "failed to find RP1 DT node\n");
+ return -EINVAL;
+ }
+
+ pcie_pdev = of_find_device_by_node(rp1_node->parent);
+ rp1->domain = irq_domain_add_linear(rp1_node, RP1_IRQS,
+ &rp1_domain_ops, rp1);
+
+ g_rp1 = rp1;
+
+ /* TODO can this go in the rp1 device tree entry? */
+ rp1->msix_cfg_regs = ioremap(rp1_io_to_phys(rp1, RP1_PCIE_APBS_BASE), 0x1000);
+
+ for (i = 0; i < RP1_IRQS; i++) {
+ int irq = irq_create_mapping(rp1->domain, i);
+
+ if (irq < 0) {
+ dev_err(&pdev->dev, "failed to create irq mapping\n");
+ return irq;
+ }
+
+ irq_set_chip_data(irq, rp1);
+ irq_set_chip_and_handler(irq, &rp1_irq_chip, handle_level_irq);
+ irq_set_probe(irq);
+ irq_set_chained_handler(pci_irq_vector(pdev, i),
+ rp1_chained_handle_irq);
+ }
+
+ if (rp1_node)
+ of_platform_populate(rp1_node, NULL, NULL, &pcie_pdev->dev);
+
+ of_node_put(rp1_node);
+
+ return 0;
+}
+
+static void rp1_remove(struct pci_dev *pdev)
+{
+ struct rp1_dev *rp1 = pci_get_drvdata(pdev);
+
+ mfd_remove_devices(&pdev->dev);
+
+ clk_unregister(rp1->sys_clk);
+}
+
+static const struct pci_device_id dev_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_RPI, PCI_DEVICE_ID_RP1_C0), },
+ { 0, }
+};
+
+static struct pci_driver rp1_driver = {
+ .name = RP1_DRIVER_NAME,
+ .id_table = dev_id_table,
+ .probe = rp1_probe,
+ .remove = rp1_remove,
+};
+
+module_pci_driver(rp1_driver);
+
+MODULE_AUTHOR("Phil Elwell <[email protected]>");
+MODULE_DESCRIPTION("RP1 wrapper");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c
index 0a607a1e3ca1..5074a94fda5b 100644
--- a/drivers/mfd/simple-mfd-i2c.c
+++ b/drivers/mfd/simple-mfd-i2c.c
@@ -29,6 +29,15 @@ static const struct regmap_config regmap_config_8r_8v = {
.val_bits = 8,
};
+static const struct regmap_config regmap_config_16r_8v = {
+ .reg_bits = 16,
+ .val_bits = 8,
+};
+
+static const struct simple_mfd_data rpi_poe_core = {
+ .regmap_config = &regmap_config_16r_8v,
+};
+
static int simple_mfd_i2c_probe(struct i2c_client *i2c)
{
const struct simple_mfd_data *simple_mfd_data;
@@ -117,6 +126,8 @@ static const struct of_device_id simple_mfd_i2c_of_match[] = {
{ .compatible = "maxim,max5970", .data = &maxim_max5970},
{ .compatible = "maxim,max5978", .data = &maxim_max5970},
{ .compatible = "maxim,max77705-battery", .data = &maxim_mon_max77705},
+ { .compatible = "raspberrypi,poe-core", &rpi_poe_core },
+ { .compatible = "raspberrypi,sensehat" },
{ .compatible = "silergy,sy7636a", .data = &silergy_sy7636a},
{ .compatible = "spacemit,p1", .data = &spacemit_p1, },
{}